feat: slight animation improvement
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user