general: text swap and attempt for menu item fix
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
@@ -180,23 +181,23 @@ struct TimerStatusRow: View {
|
||||
Spacer()
|
||||
|
||||
#if DEBUG
|
||||
if let onDevTrigger = onDevTrigger {
|
||||
Button(action: onDevTrigger) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.yellow)
|
||||
.padding(6)
|
||||
.background(
|
||||
Circle()
|
||||
.fill(isHoveredDevTrigger ? Color.yellow.opacity(0.35) : Color.clear)
|
||||
)
|
||||
if let onDevTrigger = onDevTrigger {
|
||||
Button(action: onDevTrigger) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.yellow)
|
||||
.padding(6)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.glassEffect(
|
||||
isHoveredDevTrigger ? .regular.tint(.yellow) : .regular,
|
||||
in: .circle
|
||||
)
|
||||
.help("Trigger \(type.displayName) reminder now (dev)")
|
||||
.onHover { hovering in
|
||||
isHoveredDevTrigger = hovering
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.help("Trigger \(type.displayName) reminder now (dev)")
|
||||
.onHover { hovering in
|
||||
isHoveredDevTrigger = hovering
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Button(action: onSkip) {
|
||||
@@ -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 {
|
||||
|
||||
@@ -74,7 +74,8 @@ struct OnboardingContainerView: View {
|
||||
}
|
||||
|
||||
SettingsOnboardingView(
|
||||
launchAtLogin: $launchAtLogin
|
||||
launchAtLogin: $launchAtLogin,
|
||||
isOnboarding: true
|
||||
)
|
||||
.tag(4)
|
||||
.tabItem {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -64,7 +64,8 @@ struct SettingsWindowView: View {
|
||||
}
|
||||
|
||||
SettingsOnboardingView(
|
||||
launchAtLogin: $launchAtLogin
|
||||
launchAtLogin: $launchAtLogin,
|
||||
isOnboarding: false
|
||||
)
|
||||
.tag(3)
|
||||
.tabItem {
|
||||
|
||||
Reference in New Issue
Block a user