Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
dbaf570ab7
commit
233dfc182a
@ -53,8 +53,9 @@ enum AppConstants {
|
|||||||
static let digitScale = 1.0
|
static let digitScale = 1.0
|
||||||
static let clockOpacity = 0.5
|
static let clockOpacity = 0.5
|
||||||
static let overlayOpacity = 0.5
|
static let overlayOpacity = 0.5
|
||||||
static let maxFontSize = 220.0
|
static let minFontSize = 20.0
|
||||||
static let safeInset = 16.0 // Reasonable safe inset
|
static let maxFontSize = 500.0
|
||||||
|
static let safeInset = 4.0 // Reasonable safe inset
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - System Sounds
|
// MARK: - System Sounds
|
||||||
|
|||||||
@ -217,28 +217,7 @@ enum FontUtils {
|
|||||||
return .custom(family, size: size)
|
return .custom(family, size: size)
|
||||||
.weight(fontWeight)
|
.weight(fontWeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the maximum width for any two-digit combination
|
|
||||||
/// - Parameters:
|
|
||||||
/// - font: The font to measure with
|
|
||||||
/// - fontSize: The font size
|
|
||||||
/// - Returns: Maximum width for any two-digit combination
|
|
||||||
static func maxTwoDigitWidth(font: UIFont, fontSize: CGFloat) -> CGFloat {
|
|
||||||
let digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
|
|
||||||
var maxWidth: CGFloat = 0
|
|
||||||
|
|
||||||
// Test all possible two-digit combinations to find the widest
|
|
||||||
for firstDigit in digits {
|
|
||||||
for secondDigit in digits {
|
|
||||||
let combination = firstDigit + secondDigit
|
|
||||||
let width = measureTextSize(text: combination, font: font).width
|
|
||||||
maxWidth = max(maxWidth, width)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get dot size multiplier to match visual weight of font
|
/// Get dot size multiplier to match visual weight of font
|
||||||
/// - Parameter weight: Font weight name
|
/// - Parameter weight: Font weight name
|
||||||
/// - Returns: Multiplier for dot size (0.3 to 1.0) - much smaller for lighter weights
|
/// - Returns: Multiplier for dot size (0.3 to 1.0) - much smaller for lighter weights
|
||||||
@ -338,19 +317,11 @@ enum FontUtils {
|
|||||||
return (text as NSString).size(withAttributes: attributes)
|
return (text as NSString).size(withAttributes: attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the maximum character width for digits to ensure consistent spacing
|
// Calculate height of text for the given font - this ensures consistent height
|
||||||
/// - Parameter font: The font to measure with
|
static func calculateMaxTextSize(font: UIFont) -> CGSize {
|
||||||
/// - Returns: The width of the widest digit character
|
let attributes = [NSAttributedString.Key.font: font]
|
||||||
static func maxDigitWidth(font: UIFont) -> CGFloat {
|
let size = ("8" as NSString).size(withAttributes: attributes)
|
||||||
let digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
|
return size
|
||||||
var maxWidth: CGFloat = 0
|
|
||||||
|
|
||||||
for digit in digits {
|
|
||||||
let size = measureTextSize(text: digit, font: font)
|
|
||||||
maxWidth = max(maxWidth, size.width)
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxWidth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate fixed-width layout size for time display to prevent jumping
|
/// Calculate fixed-width layout size for time display to prevent jumping
|
||||||
@ -366,8 +337,9 @@ enum FontUtils {
|
|||||||
showAmPm: Bool,
|
showAmPm: Bool,
|
||||||
isPortrait: Bool
|
isPortrait: Bool
|
||||||
) -> (width: CGFloat, height: CGFloat) {
|
) -> (width: CGFloat, height: CGFloat) {
|
||||||
let digitWidth = maxDigitWidth(font: font)
|
let digitSize = calculateMaxTextSize(font: font)
|
||||||
let digitHeight = measureTextSize(text: "8", font: font).height // Use 8 as reference height
|
let digitWidth = digitSize.width
|
||||||
|
let digitHeight = digitSize.height
|
||||||
|
|
||||||
// Calculate separator sizes
|
// Calculate separator sizes
|
||||||
let dotDiameter = font.pointSize * 0.20
|
let dotDiameter = font.pointSize * 0.20
|
||||||
@ -429,12 +401,11 @@ enum FontUtils {
|
|||||||
isPortrait: Bool,
|
isPortrait: Bool,
|
||||||
showSeconds: Bool = false
|
showSeconds: Bool = false
|
||||||
) -> CGFloat {
|
) -> CGFloat {
|
||||||
// Use reasonable safe areas
|
// Calculate optimal size with reasonable space usage
|
||||||
let safeInset = AppConstants.Defaults.safeInset
|
let safeInset = AppConstants.Defaults.safeInset
|
||||||
let availableWidth = max(1, containerWidth - safeInset * 2)
|
let availableWidth = max(1, containerWidth - safeInset * 2)
|
||||||
let availableHeight = max(1, containerHeight - safeInset * 2)
|
let availableHeight = max(1, containerHeight - safeInset * 2)
|
||||||
|
|
||||||
// Calculate optimal size with reasonable space usage
|
|
||||||
let optimalSize: CGFloat
|
let optimalSize: CGFloat
|
||||||
if isPortrait {
|
if isPortrait {
|
||||||
// In portrait, use most of the available height
|
// In portrait, use most of the available height
|
||||||
@ -451,8 +422,7 @@ enum FontUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply reasonable bounds
|
// Apply reasonable bounds
|
||||||
let minSize: CGFloat = 20
|
let minSize: CGFloat = AppConstants.Defaults.minFontSize
|
||||||
let maxSize: CGFloat = AppConstants.Defaults.maxFontSize
|
return max(minSize, optimalSize)
|
||||||
return max(minSize, min(optimalSize, maxSize))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ struct DigitView: View {
|
|||||||
.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
|
.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: digitWidth, height: digitHeight)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
|
|||||||
@ -56,76 +56,40 @@ struct TimeDisplayView: View {
|
|||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
let size = proxy.size
|
let containerSize = proxy.size
|
||||||
let portrait = size.height >= size.width
|
let portrait = containerSize.height >= containerSize.width
|
||||||
|
|
||||||
// Get safe area information for font sizing to avoid Dynamic Island overlap
|
|
||||||
let safeAreaInsets = proxy.safeAreaInsets
|
|
||||||
let safeWidth = size.width - safeAreaInsets.leading - safeAreaInsets.trailing // availableWidth
|
|
||||||
let safeHeight = size.height - safeAreaInsets.top - safeAreaInsets.bottom // availableHeight
|
|
||||||
let fullScreenSize = CGSize(width: safeWidth, height: safeHeight)
|
|
||||||
|
|
||||||
// Use optimal font sizing that maximizes space usage with full screen
|
// Use optimal font sizing based on actual container size
|
||||||
let baseFontSize = stretched ?
|
let baseFontSize = stretched ?
|
||||||
FontUtils.maximumStretchedFontSize(
|
FontUtils.maximumStretchedFontSize(
|
||||||
containerWidth: fullScreenSize.width,
|
containerWidth: containerSize.width,
|
||||||
containerHeight: fullScreenSize.height,
|
containerHeight: containerSize.height,
|
||||||
isPortrait: portrait,
|
isPortrait: portrait,
|
||||||
showSeconds: showSeconds
|
showSeconds: showSeconds
|
||||||
) :
|
) :
|
||||||
FontUtils.optimalFontSize(
|
FontUtils.optimalFontSize(
|
||||||
containerWidth: fullScreenSize.width,
|
containerWidth: containerSize.width,
|
||||||
containerHeight: fullScreenSize.height,
|
containerHeight: containerSize.height,
|
||||||
isPortrait: portrait,
|
isPortrait: portrait,
|
||||||
showSeconds: showSeconds
|
showSeconds: showSeconds
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DEBUG: Print font size and container size
|
||||||
|
let _ = print("DEBUG: Container: \(containerSize), Font: \(baseFontSize), Stretched: \(stretched)")
|
||||||
|
|
||||||
// Time components
|
// Time components
|
||||||
let hour = use24Hour ? Self.hour24DF.string(from: date) : Self.hour12DF.string(from: date)
|
let hour = use24Hour ? Self.hour24DF.string(from: date) : Self.hour12DF.string(from: date)
|
||||||
let minute = Self.minuteDF.string(from: date)
|
let minute = Self.minuteDF.string(from: date)
|
||||||
let secondsText = Self.secondDF.string(from: date)
|
let secondsText = Self.secondDF.string(from: date)
|
||||||
|
|
||||||
// Calculate sizes using fixed-width approach to prevent jumping
|
|
||||||
let digitUIFont = FontUtils.customUIFont(
|
|
||||||
size: baseFontSize,
|
|
||||||
family: fontFamily,
|
|
||||||
weight: fontWeight,
|
|
||||||
design: fontDesign
|
|
||||||
)
|
|
||||||
|
|
||||||
// Calculate consistent sizes for layout
|
|
||||||
let textHeight = measureText("8", font: digitUIFont).height // Use 8 as reference height
|
|
||||||
|
|
||||||
// Separators - reasonable spacing with extra padding in landscape
|
// Separators - reasonable spacing with extra padding in landscape
|
||||||
let dotDiameter = baseFontSize * 0.20
|
let dotDiameter = baseFontSize * 0.20
|
||||||
let hSpacing = portrait ? baseFontSize * 0.18 : baseFontSize * 0.25 // More spacing in landscape
|
let hSpacing = portrait ? baseFontSize * 0.18 : baseFontSize * 0.25 // More spacing in landscape
|
||||||
let vSpacing = portrait ? baseFontSize * 0.22 : baseFontSize * 0.30 // More spacing in landscape
|
let vSpacing = portrait ? baseFontSize * 0.22 : baseFontSize * 0.30 // More spacing in landscape
|
||||||
let horizontalSepSize = CGSize(width: dotDiameter * 2 + hSpacing, height: dotDiameter)
|
|
||||||
let verticalSepSize = CGSize(width: dotDiameter, height: dotDiameter * 2 + vSpacing)
|
|
||||||
|
|
||||||
// Calculate layout - simplified without AM/PM
|
// Simple scaling - let the content size naturally and apply manual scale
|
||||||
let (totalWidth, totalHeight) = calculateLayoutSize(
|
let finalScale = stretched ? 1.0 : CGFloat(max(0.1, min(manualScale, 1.0)))
|
||||||
portrait: portrait,
|
|
||||||
horizontalSepSize: horizontalSepSize,
|
|
||||||
verticalSepSize: verticalSepSize,
|
|
||||||
showSeconds: showSeconds
|
|
||||||
)
|
|
||||||
|
|
||||||
// Calculate scale with maximum space utilization using full screen
|
|
||||||
let safeInset = AppConstants.Defaults.safeInset
|
|
||||||
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)
|
|
||||||
let heightScale = availableH / max(totalHeight, 1)
|
|
||||||
|
|
||||||
// For stretched mode, use reasonable scaling
|
|
||||||
let effectiveScale = stretched ?
|
|
||||||
max(0.1, min(min(widthScale, heightScale), 1.5)) : // Use min scale and cap at 1.5x to prevent overflow
|
|
||||||
max(0.1, max(0.1, min(widthScale, heightScale)) * CGFloat(max(0.1, min(manualScale, 1.0))))
|
|
||||||
|
|
||||||
let finalScale = effectiveScale
|
|
||||||
|
|
||||||
// Time display with consistent centering and stable layout
|
// Time display with consistent centering and stable layout
|
||||||
Group {
|
Group {
|
||||||
@ -158,32 +122,12 @@ struct TimeDisplayView: View {
|
|||||||
.minimumScaleFactor(0.1)
|
.minimumScaleFactor(0.1)
|
||||||
.clipped() // Prevent overflow beyond bounds
|
.clipped() // Prevent overflow beyond bounds
|
||||||
}
|
}
|
||||||
.border(.blue, width: 1)
|
.border(Color.blue, width: 1)
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.onOrientationChange() // Force updates on orientation changes
|
.onOrientationChange() // Force updates on orientation changes
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helper Methods
|
|
||||||
private func measureText(_ text: String, font: UIFont) -> CGSize {
|
|
||||||
return FontUtils.measureTextSize(text: text, font: font)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func calculateLayoutSize(
|
|
||||||
portrait: Bool,
|
|
||||||
horizontalSepSize: CGSize,
|
|
||||||
verticalSepSize: CGSize,
|
|
||||||
showSeconds: Bool
|
|
||||||
) -> (CGFloat, CGFloat) {
|
|
||||||
// Simplified layout calculation without AM/PM
|
|
||||||
// This is just a placeholder since we're using natural sizing now
|
|
||||||
if portrait {
|
|
||||||
let totalH = horizontalSepSize.height * (showSeconds ? 2 : 1)
|
|
||||||
return (0, totalH) // Width will be determined by content
|
|
||||||
} else {
|
|
||||||
let totalW = verticalSepSize.width * (showSeconds ? 2 : 1)
|
|
||||||
return (totalW, 0) // Height will be determined by content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -33,8 +33,10 @@ struct TimeSegment: View {
|
|||||||
digitWidth: singleDigitWidth,
|
digitWidth: singleDigitWidth,
|
||||||
digitHeight: singleDigitHeight
|
digitHeight: singleDigitHeight
|
||||||
)
|
)
|
||||||
|
.border(Color.yellow, width: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
|
|||||||
@ -39,7 +39,6 @@ struct TopOverlayView: View {
|
|||||||
}
|
}
|
||||||
.padding(.horizontal, UIConstants.Spacing.medium)
|
.padding(.horizontal, UIConstants.Spacing.medium)
|
||||||
.padding(.vertical, UIConstants.Spacing.small)
|
.padding(.vertical, UIConstants.Spacing.small)
|
||||||
.cardStyle()
|
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
batteryService.startMonitoring()
|
batteryService.startMonitoring()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user