mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 23:24:32 +00:00
103 lines
3.6 KiB
Swift
103 lines
3.6 KiB
Swift
//
|
|
// IdleAnimationView.swift
|
|
// DynamicIsland
|
|
//
|
|
// Created by AI Assistant on 11/10/2025.
|
|
// Unified view for displaying idle animations (Lottie or built-in face)
|
|
//
|
|
|
|
import SwiftUI
|
|
import Lottie
|
|
import LottieUI
|
|
import Defaults
|
|
|
|
struct IdleAnimationView: View {
|
|
@Default(.selectedIdleAnimation) var selectedAnimation
|
|
@Default(.animationTransformOverrides) var overrides
|
|
|
|
var body: some View {
|
|
Group {
|
|
if let animation = selectedAnimation {
|
|
AnimationContentView(animation: animation)
|
|
.id("\(animation.id)-\(overrides[animation.id.uuidString]?.hashValue ?? 0)") // Force recreation when override changes
|
|
} else {
|
|
// Fallback to original face if nothing selected
|
|
MinimalFaceFeatures(height: 20, width: 30)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Internal view that renders the actual animation content
|
|
private struct AnimationContentView: View {
|
|
let animation: CustomIdleAnimation
|
|
|
|
var body: some View {
|
|
let config = animation.getTransformConfig()
|
|
|
|
// Debug logging
|
|
let _ = print("🎨 [IdleAnimationView] Rendering animation: \(animation.name)")
|
|
let _ = print("🎨 [IdleAnimationView] Config: scale=\(config.scale), offset=(\(config.offsetX), \(config.offsetY)), opacity=\(config.opacity)")
|
|
|
|
switch animation.source {
|
|
case .lottieFile(let url):
|
|
LottieView(state: LUStateData(
|
|
type: .loadedFrom(url),
|
|
speed: animation.speed,
|
|
loopMode: config.loopMode.lottieLoopMode
|
|
))
|
|
.id(animation.id) // Force reload when animation changes
|
|
.frame(
|
|
width: config.cropWidth * config.scale,
|
|
height: config.cropHeight * config.scale
|
|
)
|
|
.offset(x: config.offsetX, y: config.offsetY)
|
|
.rotationEffect(.degrees(config.rotation))
|
|
.opacity(config.opacity)
|
|
.padding(.bottom, config.paddingBottom)
|
|
.frame(width: config.expandWithAnimation ? nil : 30, height: 20)
|
|
.clipped()
|
|
|
|
case .lottieURL(let url):
|
|
LottieView(state: LUStateData(
|
|
type: .loadedFrom(url),
|
|
speed: animation.speed,
|
|
loopMode: config.loopMode.lottieLoopMode
|
|
))
|
|
.id(animation.id) // Force reload when animation changes
|
|
.frame(
|
|
width: config.cropWidth * config.scale,
|
|
height: config.cropHeight * config.scale
|
|
)
|
|
.offset(x: config.offsetX, y: config.offsetY)
|
|
.rotationEffect(.degrees(config.rotation))
|
|
.opacity(config.opacity)
|
|
.padding(.bottom, config.paddingBottom)
|
|
.frame(width: config.expandWithAnimation ? nil : 30, height: 20)
|
|
.clipped()
|
|
|
|
case .builtInFace:
|
|
// Apply transforms to built-in face too!
|
|
MinimalFaceFeatures(height: 20, width: 30)
|
|
.frame(
|
|
width: config.cropWidth * config.scale,
|
|
height: config.cropHeight * config.scale
|
|
)
|
|
.offset(x: config.offsetX, y: config.offsetY)
|
|
.rotationEffect(.degrees(config.rotation))
|
|
.opacity(config.opacity)
|
|
.padding(.bottom, config.paddingBottom)
|
|
.frame(width: config.expandWithAnimation ? nil : 30, height: 20)
|
|
.clipped()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
#Preview {
|
|
ZStack {
|
|
Color.black
|
|
IdleAnimationView()
|
|
}
|
|
.frame(width: 100, height: 50)
|
|
}
|