From a1a14172df5d0452a43a815c848cd734c0b396b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Thu, 17 Sep 2026 19:57:30 +0200 Subject: [PATCH 1/2] Larger Snoozer text --- LoopFollow/Snoozer/SnoozerView.swift | 52 ++++++++++++---------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/LoopFollow/Snoozer/SnoozerView.swift b/LoopFollow/Snoozer/SnoozerView.swift index f70daaf8c..ea5d12b57 100644 --- a/LoopFollow/Snoozer/SnoozerView.swift +++ b/LoopFollow/Snoozer/SnoozerView.swift @@ -48,6 +48,7 @@ struct SnoozerView: View { } else { VStack(spacing: 0) { leftColumn(isLandscape: false, barShowing: barShowing) + .layoutPriority(1) rightColumn(isLandscape: false) } } @@ -157,10 +158,10 @@ struct SnoozerView: View { private func leftColumn(isLandscape: Bool, barShowing: Bool) -> some View { let topPad: CGFloat = barShowing ? 0 : 16 - let bigMaxH: CGFloat = barShowing ? (isLandscape ? 210 : 220) : 240 - let dirMaxH: CGFloat = barShowing ? (isLandscape ? 72 : 72) : 80 - let deltaMaxH: CGFloat = barShowing ? (isLandscape ? 60 : 60) : 68 - let ageMaxH: CGFloat = barShowing ? 36 : 40 + let bigMaxH: CGFloat = barShowing ? (isLandscape ? 210 : 200) : 240 + let dirMaxH: CGFloat = barShowing ? (isLandscape ? 72 : 90) : (isLandscape ? 80 : 110) + let deltaMaxH: CGFloat = barShowing ? (isLandscape ? 60 : 80) : (isLandscape ? 68 : 100) + let ageMaxH: CGFloat = barShowing ? (isLandscape ? 36 : 60) : (isLandscape ? 40 : 80) return VStack(spacing: 0) { if !isLandscape && showDisplayName.value { @@ -185,29 +186,29 @@ struct SnoozerView: View { Text(directionText.value) .font(.system(size: 90, weight: .black)) Text(deltaText.value) - .font(.system(size: 70)) + .font(.system(size: 70).monospacedDigit()) } .minimumScaleFactor(0.5) .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: dirMaxH) } else { Text(directionText.value) - .font(.system(size: 110, weight: .black)) + .font(.system(size: 160, weight: .black)) .minimumScaleFactor(0.5) .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: dirMaxH) Text(deltaText.value) - .font(.system(size: 70)) + .font(.system(size: 140).monospacedDigit()) .minimumScaleFactor(0.5) - .foregroundColor(.white.opacity(0.8)) + .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: deltaMaxH) } Text(minAgoText.value) - .font(.system(size: 60)) + .font(.system(size: isLandscape ? 60 : 80).monospacedDigit()) .minimumScaleFactor(0.5) - .foregroundColor(.white.opacity(0.6)) + .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: ageMaxH) } .padding(.top, topPad) @@ -224,32 +225,23 @@ struct SnoozerView: View { .padding(.bottom, 8) } - if snoozerEmoji.value { - TimelineView(.periodic(from: .now, by: 1)) { context in - VStack(spacing: 4) { + TimelineView(.periodic(from: .now, by: 1)) { context in + VStack(spacing: 4) { + if snoozerEmoji.value { Text(bgEmoji) .font(.system(size: 128)) .minimumScaleFactor(0.5) - - Text(context.date, format: Date.FormatStyle(date: .omitted, time: .shortened)) - .font(.system(size: 70)) - .minimumScaleFactor(0.5) - .foregroundColor(.white) - .frame(height: 78) - } - } - } else { - TimelineView(.periodic(from: .now, by: 1)) { context in - VStack(spacing: 4) { - Text(context.date, format: Date.FormatStyle(date: .omitted, time: .shortened)) - .font(.system(size: 70)) - .minimumScaleFactor(0.5) - .foregroundColor(.white) - .frame(height: 78) + .frame(minHeight: isLandscape ? nil : 70) } + + Text(context.date, format: Date.FormatStyle(date: .omitted, time: .shortened)) + .font(.system(size: isLandscape ? 70 : 100).monospacedDigit()) + .minimumScaleFactor(0.5) + .foregroundColor(.white) + .frame(minHeight: isLandscape ? 78 : 80, maxHeight: isLandscape ? 78 : 100) } } - Spacer() + Spacer(minLength: isLandscape ? nil : 16) } } From 9eba111fa5d99fbc198aa5a2f3babe7190cc3b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 10 Oct 2026 20:49:54 +0200 Subject: [PATCH 2/2] Keep the Snoozer emoji and clock above the tab bar on small phones In portrait the left column's heights scale down together to leave room for the emoji and clock, and its text can shrink to 30% to fit. --- LoopFollow/Snoozer/SnoozerView.swift | 64 +++++++++++++++++++++------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/LoopFollow/Snoozer/SnoozerView.swift b/LoopFollow/Snoozer/SnoozerView.swift index ea5d12b57..b95310460 100644 --- a/LoopFollow/Snoozer/SnoozerView.swift +++ b/LoopFollow/Snoozer/SnoozerView.swift @@ -46,10 +46,16 @@ struct SnoozerView: View { rightColumn(isLandscape: true) } } else { - VStack(spacing: 0) { - leftColumn(isLandscape: false, barShowing: barShowing) + GeometryReader { content in + VStack(spacing: 0) { + leftColumn( + isLandscape: false, + barShowing: barShowing, + availableHeight: content.size.height - portraitRightColumnMinHeight + ) .layoutPriority(1) - rightColumn(isLandscape: false) + rightColumn(isLandscape: false) + } } } } @@ -156,23 +162,49 @@ struct SnoozerView: View { // MARK: - Columns - private func leftColumn(isLandscape: Bool, barShowing: Bool) -> some View { + private let portraitEmojiMinHeight: CGFloat = 70 + private let portraitClockMinHeight: CGFloat = 80 + private let portraitClockBottomSpace: CGFloat = 16 + private let displayNameHeight: CGFloat = 60 + + /// Height the emoji and clock need below the left column in portrait. + private var portraitRightColumnMinHeight: CGFloat { + let emoji = snoozerEmoji.value ? portraitEmojiMinHeight + 4 : 0 + return emoji + portraitClockMinHeight + portraitClockBottomSpace + 8 + } + + /// The left column's heights are scaled down together when `availableHeight` + /// is smaller than their combined maximum, so the column never pushes the + /// emoji and clock off screen. + private func leftColumn(isLandscape: Bool, barShowing: Bool, availableHeight: CGFloat? = nil) -> some View { let topPad: CGFloat = barShowing ? 0 : 16 - let bigMaxH: CGFloat = barShowing ? (isLandscape ? 210 : 200) : 240 - let dirMaxH: CGFloat = barShowing ? (isLandscape ? 72 : 90) : (isLandscape ? 80 : 110) - let deltaMaxH: CGFloat = barShowing ? (isLandscape ? 60 : 80) : (isLandscape ? 68 : 100) - let ageMaxH: CGFloat = barShowing ? (isLandscape ? 36 : 60) : (isLandscape ? 40 : 80) + let nameH: CGFloat = (!isLandscape && showDisplayName.value) ? displayNameHeight : 0 + let maxHeights: [CGFloat] = [ + barShowing ? (isLandscape ? 210 : 200) : 240, + barShowing ? (isLandscape ? 72 : 90) : (isLandscape ? 80 : 110), + barShowing ? (isLandscape ? 60 : 80) : (isLandscape ? 68 : 100), + barShowing ? (isLandscape ? 36 : 60) : (isLandscape ? 40 : 80), + ] + let fit: CGFloat = availableHeight.map { + min(1, max(0, $0 - topPad - nameH) / maxHeights.reduce(0, +)) + } ?? 1 + let bigMaxH = maxHeights[0] * fit + let dirMaxH = maxHeights[1] * fit + let deltaMaxH = maxHeights[2] * fit + let ageMaxH = maxHeights[3] * fit + let minScale: CGFloat = isLandscape ? 0.5 : 0.3 return VStack(spacing: 0) { if !isLandscape && showDisplayName.value { Text(Bundle.main.displayName) .font(.system(size: 50, weight: .bold)) .foregroundColor(.white.opacity(0.9)) + .frame(height: displayNameHeight) } Text(bgText.value) .font(.system(size: 300, weight: .black)) - .minimumScaleFactor(0.5) + .minimumScaleFactor(minScale) .foregroundColor(bgTextColor.value) .strikethrough( bgStale.value, @@ -188,26 +220,26 @@ struct SnoozerView: View { Text(deltaText.value) .font(.system(size: 70).monospacedDigit()) } - .minimumScaleFactor(0.5) + .minimumScaleFactor(minScale) .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: dirMaxH) } else { Text(directionText.value) .font(.system(size: 160, weight: .black)) - .minimumScaleFactor(0.5) + .minimumScaleFactor(minScale) .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: dirMaxH) Text(deltaText.value) .font(.system(size: 140).monospacedDigit()) - .minimumScaleFactor(0.5) + .minimumScaleFactor(minScale) .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: deltaMaxH) } Text(minAgoText.value) .font(.system(size: isLandscape ? 60 : 80).monospacedDigit()) - .minimumScaleFactor(0.5) + .minimumScaleFactor(minScale) .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: ageMaxH) } @@ -231,17 +263,17 @@ struct SnoozerView: View { Text(bgEmoji) .font(.system(size: 128)) .minimumScaleFactor(0.5) - .frame(minHeight: isLandscape ? nil : 70) + .frame(minHeight: isLandscape ? nil : portraitEmojiMinHeight) } Text(context.date, format: Date.FormatStyle(date: .omitted, time: .shortened)) .font(.system(size: isLandscape ? 70 : 100).monospacedDigit()) .minimumScaleFactor(0.5) .foregroundColor(.white) - .frame(minHeight: isLandscape ? 78 : 80, maxHeight: isLandscape ? 78 : 100) + .frame(minHeight: isLandscape ? 78 : portraitClockMinHeight, maxHeight: isLandscape ? 78 : 100) } } - Spacer(minLength: isLandscape ? nil : 16) + Spacer(minLength: isLandscape ? nil : portraitClockBottomSpace) } }