Messed up some stuff

This commit is contained in:
Hariharan Mudaliar 2025-10-11 14:20:23 +05:30
commit 56cb69d856
7 changed files with 85 additions and 30 deletions

View file

@ -9171,6 +9171,12 @@
}, },
"Live Performance Data" : { "Live Performance Data" : {
},
"Loop Mode" : {
},
"Loop Mode:" : {
}, },
"Lottie JSON URL" : { "Lottie JSON URL" : {
"localizations" : { "localizations" : {

View file

@ -18,6 +18,7 @@ struct IdleAnimationView: View {
Group { Group {
if let animation = selectedAnimation { if let animation = selectedAnimation {
AnimationContentView(animation: animation) AnimationContentView(animation: animation)
.id("\(animation.id)-\(animation.hashValue)") // Force complete recreation when ANY property changes
} else { } else {
// Fallback to original face if nothing selected // Fallback to original face if nothing selected
MinimalFaceFeatures(height: 20, width: 30) MinimalFaceFeatures(height: 20, width: 30)
@ -38,8 +39,9 @@ private struct AnimationContentView: View {
LottieView(state: LUStateData( LottieView(state: LUStateData(
type: .loadedFrom(url), type: .loadedFrom(url),
speed: animation.speed, speed: animation.speed,
loopMode: .loop loopMode: config.loopMode.lottieLoopMode
)) ))
.id(animation.id) // Force reload when animation changes
.frame( .frame(
width: config.cropWidth * config.scale, width: config.cropWidth * config.scale,
height: config.cropHeight * config.scale height: config.cropHeight * config.scale
@ -55,8 +57,9 @@ private struct AnimationContentView: View {
LottieView(state: LUStateData( LottieView(state: LUStateData(
type: .loadedFrom(url), type: .loadedFrom(url),
speed: animation.speed, speed: animation.speed,
loopMode: .loop loopMode: config.loopMode.lottieLoopMode
)) ))
.id(animation.id) // Force reload when animation changes
.frame( .frame(
width: config.cropWidth * config.scale, width: config.cropWidth * config.scale,
height: config.cropHeight * config.scale height: config.cropHeight * config.scale

View file

@ -29,6 +29,7 @@ struct AnimationEditorView: View {
@State private var opacity: CGFloat = 1.0 @State private var opacity: CGFloat = 1.0
@State private var paddingBottom: CGFloat = 0 @State private var paddingBottom: CGFloat = 0
@State private var expandWithAnimation: Bool = false @State private var expandWithAnimation: Bool = false
@State private var loopMode: AnimationLoopMode = .loop
@State private var showingError = false @State private var showingError = false
@State private var errorMessage = "" @State private var errorMessage = ""
@ -57,6 +58,7 @@ struct AnimationEditorView: View {
_opacity = State(initialValue: config.opacity) _opacity = State(initialValue: config.opacity)
_paddingBottom = State(initialValue: config.paddingBottom) _paddingBottom = State(initialValue: config.paddingBottom)
_expandWithAnimation = State(initialValue: config.expandWithAnimation) _expandWithAnimation = State(initialValue: config.expandWithAnimation)
_loopMode = State(initialValue: config.loopMode)
} else { } else {
let fileName = sourceURL.deletingPathExtension().lastPathComponent let fileName = sourceURL.deletingPathExtension().lastPathComponent
_name = State(initialValue: fileName) _name = State(initialValue: fileName)
@ -107,7 +109,7 @@ struct AnimationEditorView: View {
LottieView(state: LUStateData( LottieView(state: LUStateData(
type: .loadedFrom(sourceURL), type: .loadedFrom(sourceURL),
speed: speed, speed: speed,
loopMode: .loop loopMode: loopMode.lottieLoopMode
)) ))
.frame(width: cropWidth * scale * previewScale, height: cropHeight * scale * previewScale) .frame(width: cropWidth * scale * previewScale, height: cropHeight * scale * previewScale)
.offset(x: offsetX * previewScale, y: offsetY * previewScale) .offset(x: offsetX * previewScale, y: offsetY * previewScale)
@ -313,6 +315,18 @@ struct AnimationEditorView: View {
.buttonStyle(.bordered) .buttonStyle(.bordered)
.controlSize(.small) .controlSize(.small)
} }
// Loop Mode
VStack(alignment: .leading, spacing: 8) {
Text("Loop Mode:")
.font(.subheadline)
Picker("Loop Mode", selection: $loopMode) {
ForEach(AnimationLoopMode.allCases, id: \.self) { mode in
Text(mode.rawValue).tag(mode)
}
}
.pickerStyle(.segmented)
}
} }
Divider() Divider()
@ -430,7 +444,9 @@ struct AnimationEditorView: View {
} }
.padding() .padding()
} }
.frame(idealWidth: 900, idealHeight: 650)
.frame(minWidth: 800, minHeight: 600) .frame(minWidth: 800, minHeight: 600)
.fixedSize()
.alert("Import Error", isPresented: $showingError) { .alert("Import Error", isPresented: $showingError) {
Button("OK") { } Button("OK") { }
} message: { } message: {
@ -455,15 +471,21 @@ struct AnimationEditorView: View {
rotation: rotation, rotation: rotation,
opacity: opacity, opacity: opacity,
paddingBottom: paddingBottom, paddingBottom: paddingBottom,
expandWithAnimation: expandWithAnimation expandWithAnimation: expandWithAnimation,
loopMode: loopMode
) )
// If editing existing animation, update it directly // If editing existing animation, create a NEW animation object with updated values
if let existing = existingAnimation { if let existing = existingAnimation {
var updatedAnimation = existing // Create completely new animation to force view refresh
updatedAnimation.name = name let updatedAnimation = CustomIdleAnimation(
updatedAnimation.speed = speed id: existing.id, // Keep same ID so it replaces the right one
updatedAnimation.transformConfig = config name: name,
source: existing.source,
speed: speed,
isBuiltIn: existing.isBuiltIn,
transformConfig: config
)
animation = updatedAnimation animation = updatedAnimation
dismiss() dismiss()
return return

View file

@ -123,19 +123,20 @@ struct IdleAnimationsSettingsSection: View {
animation: $editedAnimation, animation: $editedAnimation,
existingAnimation: editingExistingAnimation existingAnimation: editingExistingAnimation
) )
.frame(minWidth: 800, minHeight: 600) .interactiveDismissDisabled(false)
.onChange(of: editedAnimation) { oldValue, newValue in .onChange(of: editedAnimation) { oldValue, newValue in
if let animation = newValue { if let animation = newValue {
// Animation was successfully imported/edited via editor // Animation was successfully imported/edited via editor
if let existingAnim = editingExistingAnimation { if let existingAnim = editingExistingAnimation {
// Editing existing animation - update it // Editing existing animation - update it
if var animations = customIdleAnimations as? [CustomIdleAnimation], var animations = Defaults[.customIdleAnimations]
let index = animations.firstIndex(where: { $0.id == existingAnim.id }) { if let index = animations.firstIndex(where: { $0.id == existingAnim.id }) {
animations[index] = animation animations[index] = animation
customIdleAnimations = animations Defaults[.customIdleAnimations] = animations
// Update selection if this was the selected animation // Update selection if this was the selected animation
if selectedIdleAnimation?.id == existingAnim.id { if selectedIdleAnimation?.id == existingAnim.id {
selectedIdleAnimation = animation Defaults[.selectedIdleAnimation] = animation
} }
} }
} else { } else {
@ -183,6 +184,7 @@ struct IdleAnimationsSettingsSection: View {
editorSourceURL = url editorSourceURL = url
editorIsRemote = false editorIsRemote = false
editedAnimation = nil editedAnimation = nil
editingExistingAnimation = nil // Reset to nil for new imports
showingEditor = true showingEditor = true
case .failure(let error): case .failure(let error):
@ -202,6 +204,7 @@ struct IdleAnimationsSettingsSection: View {
editorSourceURL = url editorSourceURL = url
editorIsRemote = true editorIsRemote = true
editedAnimation = nil editedAnimation = nil
editingExistingAnimation = nil // Reset to nil for new imports
showingURLSheet = false showingURLSheet = false
showingEditor = true showingEditor = true
} }

View file

@ -47,29 +47,29 @@ class IdleAnimationManager {
animations.append(contentsOf: bundledAnimations) animations.append(contentsOf: bundledAnimations)
} }
// Load previously imported user animations from storage // Get existing animations
if let userAnimations = loadStoredUserAnimations() { var existing = Defaults[.customIdleAnimations]
animations.append(contentsOf: userAnimations)
}
// Only set if empty (first launch) if existing.isEmpty {
if Defaults[.customIdleAnimations].isEmpty { // First launch - set everything
Defaults[.customIdleAnimations] = animations Defaults[.customIdleAnimations] = animations
Defaults[.selectedIdleAnimation] = builtInFace // Default to built-in face Defaults[.selectedIdleAnimation] = builtInFace
print("✅ [IdleAnimationManager] Initialized with \(animations.count) animations") print("✅ [IdleAnimationManager] First launch: Initialized with \(animations.count) animations")
} else { } else {
// Merge: add any new bundled animations that aren't already in the list // Subsequent launch - ensure all bundled animations are present
var existing = Defaults[.customIdleAnimations] let existingIDs = Set(existing.map { $0.id })
let existingNames = Set(existing.map { $0.name }) let existingNames = Set(existing.filter { $0.isBuiltIn }.map { $0.name })
for newAnim in animations where newAnim.isBuiltIn { // Add any missing bundled animations
if !existingNames.contains(newAnim.name) { for bundledAnim in animations where bundledAnim.isBuiltIn {
existing.append(newAnim) if !existingNames.contains(bundledAnim.name) {
existing.insert(bundledAnim, at: existing.firstIndex(where: { !$0.isBuiltIn }) ?? existing.count)
print("➕ [IdleAnimationManager] Added missing bundled animation: \(bundledAnim.name)")
} }
} }
Defaults[.customIdleAnimations] = existing Defaults[.customIdleAnimations] = existing
print("✅ [IdleAnimationManager] Updated with \(existing.count) animations") print("✅ [IdleAnimationManager] Subsequent launch: \(existing.count) total animations (\(animations.count - 1) bundled + built-in face)")
} }
} }

View file

@ -19,9 +19,14 @@ public final class ImageService: ImageServiceProtocol {
private init() { private init() {
let config = URLSessionConfiguration.default let config = URLSessionConfiguration.default
// Create proper cache directory path
let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let artworkCachePath = cacheDir.appendingPathComponent("artwork_cache").path
let cache = URLCache(memoryCapacity: 50 * 1024 * 1024, // 50MB let cache = URLCache(memoryCapacity: 50 * 1024 * 1024, // 50MB
diskCapacity: 100 * 1024 * 1024, // 100MB diskCapacity: 100 * 1024 * 1024, // 100MB
diskPath: "artwork_cache") diskPath: artworkCachePath)
config.urlCache = cache config.urlCache = cache
config.timeoutIntervalForRequest = 15 config.timeoutIntervalForRequest = 15
config.timeoutIntervalForResource = 30 config.timeoutIntervalForResource = 30

View file

@ -7,6 +7,7 @@
import SwiftUI import SwiftUI
import Defaults import Defaults
import Lottie
private let availableDirectories = FileManager private let availableDirectories = FileManager
.default .default
@ -54,10 +55,25 @@ struct AnimationTransformConfig: Codable, Hashable, Equatable {
var opacity: CGFloat = 1.0 var opacity: CGFloat = 1.0
var paddingBottom: CGFloat = 0 // Allow adjustment to fill notch from bottom var paddingBottom: CGFloat = 0 // Allow adjustment to fill notch from bottom
var expandWithAnimation: Bool = false // Whether notch should expand horizontally with animation var expandWithAnimation: Bool = false // Whether notch should expand horizontally with animation
var loopMode: AnimationLoopMode = .loop // Loop mode for animation
static let `default` = AnimationTransformConfig() static let `default` = AnimationTransformConfig()
} }
enum AnimationLoopMode: String, Codable, CaseIterable {
case loop = "Loop"
case playOnce = "Play Once"
case autoReverse = "Auto Reverse"
var lottieLoopMode: LottieLoopMode {
switch self {
case .loop: return .loop
case .playOnce: return .playOnce
case .autoReverse: return .autoReverse
}
}
}
enum AnimationSource: Codable, Hashable, Equatable { enum AnimationSource: Codable, Hashable, Equatable {
case lottieFile(URL) // Local file (in app support or bundle) case lottieFile(URL) // Local file (in app support or bundle)
case lottieURL(URL) // Remote URL case lottieURL(URL) // Remote URL