feat: slight animation improvement

This commit is contained in:
Michael Freno
2026-01-08 22:44:01 -05:00
parent 3fc49333c4
commit 587b300a3c
3 changed files with 58 additions and 54 deletions

View File

@@ -6,10 +6,10 @@
//
import SwiftUI
import Combine
struct AnimatedFaceView: View {
@State private var eyeOffset: CGSize = .zero
@State private var animationStep = 0
let size: CGFloat
var body: some View {
@@ -38,32 +38,26 @@ struct AnimatedFaceView: View {
}
private func startAnimation() {
let sequence: [CGSize] = [
.zero, // Center
CGSize(width: -15, height: 0), // Left
.zero, // Center
CGSize(width: 15, height: 0), // Right
.zero, // Center
CGSize(width: 0, height: -10), // Up
.zero // Center
]
animateSequence(sequence, index: 0)
// Continuous eye movement animation using spring
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
animateEyeMovement()
}
}
private func animateSequence(_ sequence: [CGSize], index: Int) {
guard index < sequence.count else {
// Loop the animation
animateSequence(sequence, index: 0)
return
private func animateEyeMovement() {
// Random, smooth eye movement using spring animation for natural effect
let randomOffset = CGSize(
width: CGFloat.random(in: -10...10),
height: CGFloat.random(in: -5...5)
)
withAnimation(.spring(duration: 1.2, bounce: 0.2)) {
eyeOffset = randomOffset
}
withAnimation(.easeInOut(duration: 0.8)) {
eyeOffset = sequence[index]
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
animateSequence(sequence, index: index + 1)
// Schedule next animation
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
animateEyeMovement()
}
}
}

View File

@@ -12,23 +12,33 @@ struct BlinkReminderView: View {
@State private var opacity: Double = 0
@State private var scale: CGFloat = 0
@State private var blinkState: BlinkState = .open
@State private var blinkProgress: Double = 0
@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
}
var body: some View {
VStack {
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)
// Custom eye design for more polished look
ZStack {
// Eye outline
Circle()
.stroke(Color.accentColor, lineWidth: 4)
.frame(width: scale * 1.2, height: scale * 1.2)
// Iris
Circle()
.fill(Color.accentColor)
.frame(width: scale * 0.6, height: scale * 0.6)
// Pupil that moves with blink
Circle()
.fill(.black)
.frame(width: scale * 0.25, height: scale * 0.25)
.offset(y: blinkProgress * -scale * 0.1) // Vertical movement during blink
}
.shadow(color: .black.opacity(0.3), radius: 8, x: 0, y: 4)
}
.opacity(opacity)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
@@ -39,10 +49,10 @@ struct BlinkReminderView: View {
}
private func startAnimation() {
// Fade in and grow
withAnimation(.easeOut(duration: 0.3)) {
// Fade in and grow with spring animation for natural feel
withAnimation(.spring(duration: 0.5, bounce: 0.2)) {
opacity = 1.0
scale = screenWidth * 0.1
scale = screenWidth * 0.12
}
// Start blinking after fade in
@@ -56,15 +66,15 @@ struct BlinkReminderView: View {
let pauseBetweenBlinks = 0.2
func blink() {
// Close eyes
withAnimation(.linear(duration: blinkDuration)) {
blinkState = .closed
// Close eyes with spring animation for natural movement
withAnimation(.spring(duration: blinkDuration, bounce: 0.0)) {
blinkProgress = 1.0
}
// Open eyes
DispatchQueue.main.asyncAfter(deadline: .now() + blinkDuration) {
withAnimation(.linear(duration: blinkDuration)) {
blinkState = .open
withAnimation(.spring(duration: blinkDuration, bounce: 0.0)) {
blinkProgress = 0.0
}
blinkCount += 1
@@ -75,7 +85,7 @@ struct BlinkReminderView: View {
blink()
}
} else {
// Fade out after all blinks
// Fade out after all blinks with smooth spring animation
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
fadeOut()
}
@@ -87,12 +97,12 @@ struct BlinkReminderView: View {
}
private func fadeOut() {
withAnimation(.easeOut(duration: 0.3)) {
withAnimation(.spring(duration: 0.5, bounce: 0.2)) {
opacity = 0
scale = screenWidth * 0.05
scale = screenWidth * 0.08
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
onDismiss()
}
}

View File

@@ -21,7 +21,7 @@ struct PostureReminderView: View {
VStack {
Image(systemName: "arrow.up.circle.fill")
.font(.system(size: scale))
.foregroundColor(.black)
.foregroundColor(.accentColor)
.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 2)
}
.opacity(opacity)