/* * Atoll (DynamicIsland) * Copyright (C) 2024-2026 Atoll Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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 { // No animation selected EmptyView() } } } } /// 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) .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) .clipped() } } } // MARK: - Preview #Preview { ZStack { Color.black IdleAnimationView() } .frame(width: 100, height: 50) }