fix: proper scaling of animations
This commit is contained in:
@@ -140,14 +140,16 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
)
|
||||
case .blinkTriggered:
|
||||
let sizePercentage = settingsManager?.settings.subtleReminderSizePercentage ?? 15.0
|
||||
contentView = AnyView(
|
||||
BlinkReminderView { [weak self] in
|
||||
BlinkReminderView(sizePercentage: sizePercentage) { [weak self] in
|
||||
self?.timerEngine?.dismissReminder()
|
||||
}
|
||||
)
|
||||
case .postureTriggered:
|
||||
let sizePercentage = settingsManager?.settings.subtleReminderSizePercentage ?? 10.0
|
||||
contentView = AnyView(
|
||||
PostureReminderView { [weak self] in
|
||||
PostureReminderView(sizePercentage: sizePercentage) { [weak self] in
|
||||
self?.timerEngine?.dismissReminder()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ struct AppSettings: Codable, Equatable, Hashable {
|
||||
var userTimers: [UserTimer]
|
||||
|
||||
// UI and display settings
|
||||
var subtleReminderSizePercentage: Double // 2-35% of screen width
|
||||
var subtleReminderSizePercentage: Double // 0.5-25% of screen width
|
||||
|
||||
// App state and behavior
|
||||
var hasCompletedOnboarding: Bool
|
||||
@@ -29,10 +29,13 @@ struct AppSettings: Codable, Equatable, Hashable {
|
||||
var playSounds: Bool
|
||||
|
||||
init(
|
||||
lookAwayTimer: TimerConfiguration = TimerConfiguration(enabled: true, intervalSeconds: 20 * 60),
|
||||
lookAwayTimer: TimerConfiguration = TimerConfiguration(
|
||||
enabled: true, intervalSeconds: 20 * 60),
|
||||
lookAwayCountdownSeconds: Int = 20,
|
||||
blinkTimer: TimerConfiguration = TimerConfiguration(enabled: false, intervalSeconds: 7 * 60),
|
||||
postureTimer: TimerConfiguration = TimerConfiguration(enabled: true, intervalSeconds: 30 * 60),
|
||||
blinkTimer: TimerConfiguration = TimerConfiguration(
|
||||
enabled: false, intervalSeconds: 7 * 60),
|
||||
postureTimer: TimerConfiguration = TimerConfiguration(
|
||||
enabled: true, intervalSeconds: 30 * 60),
|
||||
userTimers: [UserTimer] = [],
|
||||
subtleReminderSizePercentage: Double = 5.0,
|
||||
hasCompletedOnboarding: Bool = false,
|
||||
@@ -66,14 +69,12 @@ struct AppSettings: Codable, Equatable, Hashable {
|
||||
}
|
||||
|
||||
static func == (lhs: AppSettings, rhs: AppSettings) -> Bool {
|
||||
lhs.lookAwayTimer == rhs.lookAwayTimer &&
|
||||
lhs.lookAwayCountdownSeconds == rhs.lookAwayCountdownSeconds &&
|
||||
lhs.blinkTimer == rhs.blinkTimer &&
|
||||
lhs.postureTimer == rhs.postureTimer &&
|
||||
lhs.userTimers == rhs.userTimers &&
|
||||
lhs.subtleReminderSizePercentage == rhs.subtleReminderSizePercentage &&
|
||||
lhs.hasCompletedOnboarding == rhs.hasCompletedOnboarding &&
|
||||
lhs.launchAtLogin == rhs.launchAtLogin &&
|
||||
lhs.playSounds == rhs.playSounds
|
||||
lhs.lookAwayTimer == rhs.lookAwayTimer
|
||||
&& lhs.lookAwayCountdownSeconds == rhs.lookAwayCountdownSeconds
|
||||
&& lhs.blinkTimer == rhs.blinkTimer && lhs.postureTimer == rhs.postureTimer
|
||||
&& lhs.userTimers == rhs.userTimers
|
||||
&& lhs.subtleReminderSizePercentage == rhs.subtleReminderSizePercentage
|
||||
&& lhs.hasCompletedOnboarding == rhs.hasCompletedOnboarding
|
||||
&& lhs.launchAtLogin == rhs.launchAtLogin && lhs.playSounds == rhs.playSounds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,62 +5,48 @@
|
||||
// Created by Mike Freno on 1/8/26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Lottie
|
||||
import SwiftUI
|
||||
|
||||
struct LottieView: NSViewRepresentable {
|
||||
struct GazeLottieView: View {
|
||||
let animationName: String
|
||||
let loopMode: LottieLoopMode
|
||||
let animationSpeed: CGFloat
|
||||
let onAnimationFinish: ((Bool) -> Void)?
|
||||
|
||||
init(
|
||||
animationName: String,
|
||||
loopMode: LottieLoopMode = .playOnce,
|
||||
animationSpeed: CGFloat = 1.0
|
||||
animationSpeed: CGFloat = 1.0,
|
||||
onAnimationFinish: ((Bool) -> Void)? = nil
|
||||
) {
|
||||
self.animationName = animationName
|
||||
self.loopMode = loopMode
|
||||
self.animationSpeed = animationSpeed
|
||||
self.onAnimationFinish = onAnimationFinish
|
||||
}
|
||||
|
||||
func makeNSView(context: Context) -> LottieAnimationView {
|
||||
let animationView = LottieAnimationView()
|
||||
animationView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
var body: some View {
|
||||
if let animation = LottieAnimation.named(animationName) {
|
||||
animationView.animation = animation
|
||||
animationView.loopMode = loopMode
|
||||
animationView.animationSpeed = animationSpeed
|
||||
animationView.backgroundBehavior = .pauseAndRestore
|
||||
animationView.play()
|
||||
LottieView(animation: animation)
|
||||
.playing(.fromProgress(nil, toProgress: 1, loopMode: loopMode))
|
||||
.animationSpeed(animationSpeed)
|
||||
.animationDidFinish { completed in
|
||||
onAnimationFinish?(completed)
|
||||
}
|
||||
|
||||
return animationView
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: LottieAnimationView, context: Context) {
|
||||
guard nsView.animation == nil || nsView.isAnimationPlaying == false else {
|
||||
return
|
||||
}
|
||||
|
||||
if let animation = LottieAnimation.named(animationName) {
|
||||
nsView.animation = animation
|
||||
nsView.loopMode = loopMode
|
||||
nsView.animationSpeed = animationSpeed
|
||||
nsView.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Lottie Preview") {
|
||||
VStack(spacing: 20) {
|
||||
LottieView(animationName: "blink")
|
||||
GazeLottieView(animationName: "blink")
|
||||
.frame(width: 200, height: 200)
|
||||
|
||||
LottieView(animationName: "look-away", loopMode: .loop)
|
||||
GazeLottieView(animationName: "look-away", loopMode: .loop)
|
||||
.frame(width: 200, height: 200)
|
||||
|
||||
LottieView(animationName: "posture")
|
||||
GazeLottieView(animationName: "posture")
|
||||
.frame(width: 200, height: 200)
|
||||
}
|
||||
.frame(width: 600, height: 800)
|
||||
|
||||
@@ -65,10 +65,10 @@ struct SettingsOnboardingView: View {
|
||||
HStack {
|
||||
Slider(
|
||||
value: $subtleReminderSizePercentage,
|
||||
in: 2...35,
|
||||
step: 1
|
||||
in: 0.5...25,
|
||||
step: 0.5
|
||||
)
|
||||
Text("\(Int(subtleReminderSizePercentage))%")
|
||||
Text("\(String(format: "%.1f", subtleReminderSizePercentage))%")
|
||||
.frame(width: 50, alignment: .trailing)
|
||||
.monospacedDigit()
|
||||
}
|
||||
@@ -137,7 +137,8 @@ struct SettingsOnboardingView: View {
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.glassEffect(.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 10))
|
||||
.glassEffect(
|
||||
.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 10))
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
@@ -5,30 +5,44 @@
|
||||
// Created by Mike Freno on 1/7/26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Lottie
|
||||
import SwiftUI
|
||||
|
||||
struct BlinkReminderView: View {
|
||||
let sizePercentage: Double
|
||||
var onDismiss: () -> Void
|
||||
|
||||
@State private var opacity: Double = 0
|
||||
@State private var scale: CGFloat = 0
|
||||
@State private var shouldShowAnimation = false
|
||||
|
||||
private let screenHeight = NSScreen.main?.frame.height ?? 800
|
||||
private let screenWidth = NSScreen.main?.frame.width ?? 1200
|
||||
|
||||
// For now, we'll use hardcoded size but leave framework for configuration
|
||||
// In a real implementation, this would be passed in from SettingsManager
|
||||
private var baseSize: CGFloat {
|
||||
screenWidth * (sizePercentage / 100.0)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
LottieView(
|
||||
if shouldShowAnimation {
|
||||
GazeLottieView(
|
||||
animationName: AnimationAsset.blink.fileName,
|
||||
loopMode: .playOnce,
|
||||
animationSpeed: 1.0
|
||||
animationSpeed: 1.0,
|
||||
onAnimationFinish: { completed in
|
||||
if completed {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
fadeOut()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.frame(width: scale, height: scale)
|
||||
.frame(width: baseSize, height: baseSize)
|
||||
.scaleEffect(scale)
|
||||
.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 2)
|
||||
}
|
||||
}
|
||||
.opacity(opacity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
.padding(.top, screenHeight * 0.1)
|
||||
@@ -38,22 +52,20 @@ struct BlinkReminderView: View {
|
||||
}
|
||||
|
||||
private func startAnimation() {
|
||||
// Fade in and grow
|
||||
withAnimation(.easeOut(duration: 0.3)) {
|
||||
opacity = 1.0
|
||||
scale = screenWidth * 0.15
|
||||
scale = 1.0
|
||||
}
|
||||
|
||||
// Animation duration (2 seconds for double blink) + hold time
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2.3) {
|
||||
fadeOut()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
shouldShowAnimation = true
|
||||
}
|
||||
}
|
||||
|
||||
private func fadeOut() {
|
||||
withAnimation(.easeOut(duration: 0.3)) {
|
||||
opacity = 0
|
||||
scale = screenWidth * 0.1
|
||||
scale = 0.7
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
@@ -63,11 +75,11 @@ struct BlinkReminderView: View {
|
||||
}
|
||||
|
||||
#Preview("Blink Reminder") {
|
||||
BlinkReminderView(onDismiss: {})
|
||||
BlinkReminderView(sizePercentage: 15.0, onDismiss: {})
|
||||
.frame(width: 800, height: 600)
|
||||
}
|
||||
|
||||
#Preview("Blink Reminder") {
|
||||
BlinkReminderView(onDismiss: {})
|
||||
BlinkReminderView(sizePercentage: 15.0, onDismiss: {})
|
||||
.frame(width: 800, height: 600)
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
// Created by Mike Freno on 1/7/26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Lottie
|
||||
import AppKit
|
||||
import Lottie
|
||||
import SwiftUI
|
||||
|
||||
struct LookAwayReminderView: View {
|
||||
let countdownSeconds: Int
|
||||
@@ -38,7 +38,7 @@ struct LookAwayReminderView: View {
|
||||
.font(.system(size: 28))
|
||||
.foregroundColor(.white.opacity(0.9))
|
||||
|
||||
LottieView(
|
||||
GazeLottieView(
|
||||
animationName: AnimationAsset.lookAway.fileName,
|
||||
loopMode: .loop,
|
||||
animationSpeed: 0.75
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct PostureReminderView: View {
|
||||
let sizePercentage: Double
|
||||
var onDismiss: () -> Void
|
||||
|
||||
@State private var scale: CGFloat = 0
|
||||
@@ -34,17 +35,17 @@ struct PostureReminderView: View {
|
||||
}
|
||||
|
||||
private func startAnimation() {
|
||||
// Phase 1: Fade in + Grow to 10% screen width
|
||||
// Phase 1: Fade in + Grow to configured size
|
||||
withAnimation(.easeOut(duration: 0.4)) {
|
||||
opacity = 1.0
|
||||
scale = screenWidth * 0.1
|
||||
scale = screenWidth * (sizePercentage / 100.0)
|
||||
}
|
||||
|
||||
// Phase 2: Hold
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4 + 0.5) {
|
||||
// Phase 3: Shrink to 5%
|
||||
// Phase 3: Shrink to half the configured size
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
scale = screenWidth * 0.05
|
||||
scale = screenWidth * (sizePercentage / 100.0) * 0.5
|
||||
}
|
||||
|
||||
// Phase 4: Shoot upward
|
||||
@@ -64,6 +65,6 @@ struct PostureReminderView: View {
|
||||
}
|
||||
|
||||
#Preview("Posture Reminder") {
|
||||
PostureReminderView(onDismiss: {})
|
||||
PostureReminderView(sizePercentage: 10.0, onDismiss: {})
|
||||
.frame(width: 800, height: 600)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user