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() {
let popover = NSPopover()
popover.contentSize = NSSize(width: 300, height: 400)
popover.behavior = .transient
popover.contentViewController = NSHostingController(
// Reuse existing popover or create new one
if popover == nil {
let newPopover = NSPopover()
newPopover.contentSize = NSSize(width: 300, height: 400)
newPopover.behavior = .transient
popover = newPopover
}
// Always set fresh content
popover?.contentViewController = NSHostingController(
rootView: MenuBarContentView(
timerEngine: timerEngine!,
settingsManager: settingsManager!,
@@ -74,10 +80,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
)
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() {

View File

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

View File

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

View File

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

View File

@@ -11,9 +11,13 @@ struct BlinkReminderView: View {
var onDismiss: () -> Void
@State private var opacity: Double = 0
@State private var scale: CGFloat = 0
@State private var blinkState: BlinkState = .open
@State private var blinkCount = 0
private let screenHeight = NSScreen.main?.frame.height ?? 800
private let screenWidth = NSScreen.main?.frame.width ?? 1200
enum BlinkState {
case open
case closed
@@ -21,37 +25,35 @@ struct BlinkReminderView: View {
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 20)
.fill(Color.white)
.shadow(color: .black.opacity(0.3), radius: 10, x: 0, y: 5)
.frame(width: 100, height: 100)
.overlay(
BlinkingFace(isOpen: blinkState == .open)
)
Image(systemName: blinkState == .open ? "eye.circle" : "eye.slash.circle")
.font(.system(size: scale))
.foregroundColor(.accentColor)
.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 2)
}
.opacity(opacity)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.padding(.top, NSScreen.main?.frame.height ?? 800 * 0.1)
.padding(.top, screenHeight * 0.1)
.onAppear {
startAnimation()
}
}
private func startAnimation() {
// Fade in
withAnimation(.easeIn(duration: 0.3)) {
// Fade in and grow
withAnimation(.easeOut(duration: 0.3)) {
opacity = 1.0
scale = screenWidth * 0.1
}
// Start blinking after fade in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
performBlinks()
}
}
private func performBlinks() {
let blinkDuration = 0.1
let pauseBetweenBlinks = 0.5
let blinkDuration = 0.15
let pauseBetweenBlinks = 0.2
func blink() {
// Close eyes
@@ -67,7 +69,7 @@ struct BlinkReminderView: View {
blinkCount += 1
if blinkCount < 3 {
if blinkCount < 2 {
// Pause before next blink
DispatchQueue.main.asyncAfter(deadline: .now() + pauseBetweenBlinks) {
blink()
@@ -87,6 +89,7 @@ struct BlinkReminderView: View {
private func fadeOut() {
withAnimation(.easeOut(duration: 0.3)) {
opacity = 0
scale = screenWidth * 0.05
}
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") {
BlinkReminderView(onDismiss: {})
.frame(width: 800, height: 600)

View File

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