From 587b300a3c13bafbbdf606af797735f76b18b4f3 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Thu, 8 Jan 2026 22:44:01 -0500 Subject: [PATCH] feat: slight animation improvement --- Gaze/Views/Components/AnimatedFaceView.swift | 42 +++++++-------- Gaze/Views/Reminders/BlinkReminderView.swift | 54 +++++++++++-------- .../Views/Reminders/PostureReminderView.swift | 16 +++--- 3 files changed, 58 insertions(+), 54 deletions(-) diff --git a/Gaze/Views/Components/AnimatedFaceView.swift b/Gaze/Views/Components/AnimatedFaceView.swift index f55f9a9..7063862 100644 --- a/Gaze/Views/Components/AnimatedFaceView.swift +++ b/Gaze/Views/Components/AnimatedFaceView.swift @@ -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() } } } @@ -107,4 +101,4 @@ struct Arc: Shape { #Preview("Animated Face") { AnimatedFaceView(size: 200) .frame(width: 400, height: 400) -} +} \ No newline at end of file diff --git a/Gaze/Views/Reminders/BlinkReminderView.swift b/Gaze/Views/Reminders/BlinkReminderView.swift index fed942d..653b187 100644 --- a/Gaze/Views/Reminders/BlinkReminderView.swift +++ b/Gaze/Views/Reminders/BlinkReminderView.swift @@ -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() } } diff --git a/Gaze/Views/Reminders/PostureReminderView.swift b/Gaze/Views/Reminders/PostureReminderView.swift index cca2dfe..252bc91 100644 --- a/Gaze/Views/Reminders/PostureReminderView.swift +++ b/Gaze/Views/Reminders/PostureReminderView.swift @@ -9,19 +9,19 @@ import SwiftUI struct PostureReminderView: View { var onDismiss: () -> Void - + @State private var scale: CGFloat = 0 @State private var yOffset: CGFloat = 0 @State private var opacity: Double = 0 - + private let screenHeight = NSScreen.main?.frame.height ?? 800 private let screenWidth = NSScreen.main?.frame.width ?? 1200 - + var body: some 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) @@ -32,28 +32,28 @@ struct PostureReminderView: View { startAnimation() } } - + private func startAnimation() { // Phase 1: Fade in + Grow to 10% screen width withAnimation(.easeOut(duration: 0.4)) { opacity = 1.0 scale = screenWidth * 0.1 } - + // Phase 2: Hold DispatchQueue.main.asyncAfter(deadline: .now() + 0.4 + 0.5) { // Phase 3: Shrink to 5% withAnimation(.easeInOut(duration: 0.3)) { scale = screenWidth * 0.05 } - + // Phase 4: Shoot upward DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { withAnimation(.easeIn(duration: 0.4)) { yOffset = -screenHeight opacity = 0 } - + // Dismiss after animation DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { onDismiss()