general: text swap and attempt for menu item fix

This commit is contained in:
Michael Freno
2026-01-08 21:57:31 -05:00
parent a77e264210
commit 7cae818d2d
6 changed files with 71 additions and 96 deletions

View File

@@ -61,10 +61,16 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
private func showPopover() { private func showPopover() {
let popover = NSPopover() // Reuse existing popover or create new one
popover.contentSize = NSSize(width: 300, height: 400) if popover == nil {
popover.behavior = .transient let newPopover = NSPopover()
popover.contentViewController = NSHostingController( newPopover.contentSize = NSSize(width: 300, height: 400)
newPopover.behavior = .transient
popover = newPopover
}
// Always set fresh content
popover?.contentViewController = NSHostingController(
rootView: MenuBarContentView( rootView: MenuBarContentView(
timerEngine: timerEngine!, timerEngine: timerEngine!,
settingsManager: settingsManager!, settingsManager: settingsManager!,
@@ -74,10 +80,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
) )
if let button = statusItem?.button { if let button = statusItem?.button {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY) popover?.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
} }
self.popover = popover
} }
private func startTimers() { private func startTimers() {

View File

@@ -14,7 +14,8 @@ struct MenuBarButtonStyle: ButtonStyle {
.background( .background(
RoundedRectangle(cornerRadius: 6) RoundedRectangle(cornerRadius: 6)
.fill( .fill(
configuration.isPressed ? Color.accentColor.opacity(0.2) : Color.gray.opacity(0.1) configuration.isPressed
? Color.accentColor.opacity(0.2) : Color.gray.opacity(0.1)
) )
.opacity(configuration.isPressed ? 1 : 0) .opacity(configuration.isPressed ? 1 : 0)
) )
@@ -28,9 +29,9 @@ struct MenuBarHoverButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View { func makeBody(configuration: Configuration) -> some View {
configuration.label configuration.label
.background( .glassEffect(
RoundedRectangle(cornerRadius: 6) isHovered ? .regular.tint(.accentColor).interactive() : .regular,
.fill(isHovered ? Color.accentColor.opacity(0.35) : Color.clear) in: .rect(cornerRadius: 6)
) )
.contentShape(Rectangle()) .contentShape(Rectangle())
.onHover { hovering in .onHover { hovering in
@@ -186,12 +187,12 @@ struct TimerStatusRow: View {
.font(.caption) .font(.caption)
.foregroundColor(.yellow) .foregroundColor(.yellow)
.padding(6) .padding(6)
.background(
Circle()
.fill(isHoveredDevTrigger ? Color.yellow.opacity(0.35) : Color.clear)
)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.glassEffect(
isHoveredDevTrigger ? .regular.tint(.yellow) : .regular,
in: .circle
)
.help("Trigger \(type.displayName) reminder now (dev)") .help("Trigger \(type.displayName) reminder now (dev)")
.onHover { hovering in .onHover { hovering in
isHoveredDevTrigger = hovering isHoveredDevTrigger = hovering
@@ -204,25 +205,27 @@ struct TimerStatusRow: View {
.font(.caption) .font(.caption)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
.padding(6) .padding(6)
.background(
Circle()
.fill(isHoveredSkip ? Color.accentColor.opacity(0.35) : Color.clear)
)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.glassEffect(
isHoveredSkip ? .regular.tint(.accentColor) : .regular,
in: .circle
)
.help("Skip to next \(type.displayName) reminder") .help("Skip to next \(type.displayName) reminder")
.onHover { hovering in .onHover { hovering in
isHoveredSkip = hovering isHoveredSkip = hovering
} }
} }
.padding(.horizontal, 8)
.padding(.vertical, 6)
.glassEffect(
isHoveredBody ? .regular.tint(.accentColor) : .regular,
in: .rect(cornerRadius: 6)
)
.padding(.horizontal, 8)
.onHover { hovering in .onHover { hovering in
isHoveredBody = hovering isHoveredBody = hovering
}.background( }
RoundedRectangle(cornerRadius: 6).fill(
isHoveredBody ? Color.accentColor.opacity(0.35) : Color.clear)
)
.padding(.horizontal)
.padding(.vertical, 4)
} }
private var iconColor: Color { private var iconColor: Color {

View File

@@ -74,7 +74,8 @@ struct OnboardingContainerView: View {
} }
SettingsOnboardingView( SettingsOnboardingView(
launchAtLogin: $launchAtLogin launchAtLogin: $launchAtLogin,
isOnboarding: true
) )
.tag(4) .tag(4)
.tabItem { .tabItem {

View File

@@ -9,6 +9,7 @@ import SwiftUI
struct SettingsOnboardingView: View { struct SettingsOnboardingView: View {
@Binding var launchAtLogin: Bool @Binding var launchAtLogin: Bool
var isOnboarding: Bool = true
var body: some View { var body: some View {
VStack(spacing: 30) { VStack(spacing: 30) {
@@ -18,7 +19,7 @@ struct SettingsOnboardingView: View {
.font(.system(size: 80)) .font(.system(size: 80))
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
Text("Final Settings") Text(isOnboarding ? "Final Settings" : "General Settings")
.font(.system(size: 36, weight: .bold)) .font(.system(size: 36, weight: .bold))
Text("Configure app preferences and support the project") Text("Configure app preferences and support the project")
@@ -135,12 +136,14 @@ struct SettingsOnboardingView: View {
#Preview("Settings Onboarding - Launch Disabled") { #Preview("Settings Onboarding - Launch Disabled") {
SettingsOnboardingView( SettingsOnboardingView(
launchAtLogin: .constant(false) launchAtLogin: .constant(false),
isOnboarding: true
) )
} }
#Preview("Settings Onboarding - Launch Enabled") { #Preview("Settings Onboarding - Launch Enabled") {
SettingsOnboardingView( SettingsOnboardingView(
launchAtLogin: .constant(true) launchAtLogin: .constant(true),
isOnboarding: true
) )
} }

View File

@@ -11,9 +11,13 @@ struct BlinkReminderView: View {
var onDismiss: () -> Void var onDismiss: () -> Void
@State private var opacity: Double = 0 @State private var opacity: Double = 0
@State private var scale: CGFloat = 0
@State private var blinkState: BlinkState = .open @State private var blinkState: BlinkState = .open
@State private var blinkCount = 0 @State private var blinkCount = 0
private let screenHeight = NSScreen.main?.frame.height ?? 800
private let screenWidth = NSScreen.main?.frame.width ?? 1200
enum BlinkState { enum BlinkState {
case open case open
case closed case closed
@@ -21,37 +25,35 @@ struct BlinkReminderView: View {
var body: some View { var body: some View {
VStack { VStack {
RoundedRectangle(cornerRadius: 20) Image(systemName: blinkState == .open ? "eye.circle" : "eye.slash.circle")
.fill(Color.white) .font(.system(size: scale))
.shadow(color: .black.opacity(0.3), radius: 10, x: 0, y: 5) .foregroundColor(.accentColor)
.frame(width: 100, height: 100) .shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 2)
.overlay(
BlinkingFace(isOpen: blinkState == .open)
)
} }
.opacity(opacity) .opacity(opacity)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.padding(.top, NSScreen.main?.frame.height ?? 800 * 0.1) .padding(.top, screenHeight * 0.1)
.onAppear { .onAppear {
startAnimation() startAnimation()
} }
} }
private func startAnimation() { private func startAnimation() {
// Fade in // Fade in and grow
withAnimation(.easeIn(duration: 0.3)) { withAnimation(.easeOut(duration: 0.3)) {
opacity = 1.0 opacity = 1.0
scale = screenWidth * 0.1
} }
// Start blinking after fade in // Start blinking after fade in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
performBlinks() performBlinks()
} }
} }
private func performBlinks() { private func performBlinks() {
let blinkDuration = 0.1 let blinkDuration = 0.15
let pauseBetweenBlinks = 0.5 let pauseBetweenBlinks = 0.2
func blink() { func blink() {
// Close eyes // Close eyes
@@ -67,7 +69,7 @@ struct BlinkReminderView: View {
blinkCount += 1 blinkCount += 1
if blinkCount < 3 { if blinkCount < 2 {
// Pause before next blink // Pause before next blink
DispatchQueue.main.asyncAfter(deadline: .now() + pauseBetweenBlinks) { DispatchQueue.main.asyncAfter(deadline: .now() + pauseBetweenBlinks) {
blink() blink()
@@ -87,6 +89,7 @@ struct BlinkReminderView: View {
private func fadeOut() { private func fadeOut() {
withAnimation(.easeOut(duration: 0.3)) { withAnimation(.easeOut(duration: 0.3)) {
opacity = 0 opacity = 0
scale = screenWidth * 0.05
} }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
@@ -95,46 +98,6 @@ struct BlinkReminderView: View {
} }
} }
struct BlinkingFace: View {
let isOpen: Bool
var body: some View {
ZStack {
// Simple face
Circle()
.fill(Color.yellow)
.frame(width: 60, height: 60)
// Eyes
HStack(spacing: 12) {
if isOpen {
Circle()
.fill(Color.black)
.frame(width: 8, height: 8)
Circle()
.fill(Color.black)
.frame(width: 8, height: 8)
} else {
// Closed eyes (lines)
Rectangle()
.fill(Color.black)
.frame(width: 10, height: 2)
Rectangle()
.fill(Color.black)
.frame(width: 10, height: 2)
}
}
.offset(y: -8)
// Smile
Arc(startAngle: .degrees(20), endAngle: .degrees(160), clockwise: false)
.stroke(Color.black, lineWidth: 2)
.frame(width: 30, height: 15)
.offset(y: 10)
}
}
}
#Preview("Blink Reminder") { #Preview("Blink Reminder") {
BlinkReminderView(onDismiss: {}) BlinkReminderView(onDismiss: {})
.frame(width: 800, height: 600) .frame(width: 800, height: 600)

View File

@@ -64,7 +64,8 @@ struct SettingsWindowView: View {
} }
SettingsOnboardingView( SettingsOnboardingView(
launchAtLogin: $launchAtLogin launchAtLogin: $launchAtLogin,
isOnboarding: false
) )
.tag(3) .tag(3)
.tabItem { .tabItem {