diff --git a/DynamicIsland/components/Settings/IdleAnimationsSettingsSection.swift b/DynamicIsland/components/Settings/IdleAnimationsSettingsSection.swift index 57b3b9d..ae40fc5 100644 --- a/DynamicIsland/components/Settings/IdleAnimationsSettingsSection.swift +++ b/DynamicIsland/components/Settings/IdleAnimationsSettingsSection.swift @@ -11,6 +11,14 @@ import Defaults import LottieUI import UniformTypeIdentifiers +// MARK: - Editor State for Sheet Presentation +private struct EditorState: Identifiable { + let id = UUID() + let url: URL? + let isRemote: Bool + let existingAnimation: CustomIdleAnimation? +} + struct IdleAnimationsSettingsSection: View { @Default(.customIdleAnimations) var customIdleAnimations @Default(.selectedIdleAnimation) var selectedIdleAnimation @@ -54,6 +62,8 @@ struct IdleAnimationsSettingsSection: View { onEdit: { print("🔧 [Edit] Attempting to edit animation: \(animation.name)") print("🔧 [Edit] Animation source: \(animation.source)") + + // Set state immediately editingExistingAnimation = animation switch animation.source { @@ -62,20 +72,23 @@ struct IdleAnimationsSettingsSection: View { print("🔧 [Edit] File exists: \(FileManager.default.fileExists(atPath: url.path))") editorSourceURL = url editorIsRemote = false - showingEditor = true case .lottieURL(let url): print("🔧 [Edit] Lottie URL: \(url)") editorSourceURL = url editorIsRemote = true - showingEditor = true case .builtInFace: print("🔧 [Edit] Built-in face - using dummy URL") // Use a dummy URL for built-in face (editor only needs URL for preview) editorSourceURL = URL(string: "builtin://face")! editorIsRemote = false + } + + // Show editor with slight delay to ensure state is set + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { showingEditor = true + print("🔧 [Edit] Sheet should now be visible") } } ) @@ -130,42 +143,50 @@ struct IdleAnimationsSettingsSection: View { onCancel: { showingURLSheet = false } ) } - .sheet(isPresented: $showingEditor) { - if let url = editorSourceURL { - AnimationEditorView( - sourceURL: url, - isRemoteURL: editorIsRemote, - animation: $editedAnimation, - existingAnimation: editingExistingAnimation - ) - .interactiveDismissDisabled(false) - .onChange(of: editedAnimation) { oldValue, newValue in - if let animation = newValue { - // Animation was successfully imported/edited via editor - if let existingAnim = editingExistingAnimation { - // Editing existing animation - update it - var animations = Defaults[.customIdleAnimations] - if let index = animations.firstIndex(where: { $0.id == existingAnim.id }) { - animations[index] = animation - Defaults[.customIdleAnimations] = animations - - // Update selection if this was the selected animation - if selectedIdleAnimation?.id == existingAnim.id { - Defaults[.selectedIdleAnimation] = animation - } - } - } else { - // New import - just select it - withAnimation { - selectedIdleAnimation = animation + .sheet(item: Binding( + get: { showingEditor ? EditorState(url: editorSourceURL, isRemote: editorIsRemote, existingAnimation: editingExistingAnimation) : nil }, + set: { newValue in + showingEditor = (newValue != nil) + if newValue == nil { + // Clean up when sheet dismisses + editorSourceURL = nil + editingExistingAnimation = nil + } + } + )) { state in + AnimationEditorView( + sourceURL: state.url ?? URL(string: "builtin://face")!, + isRemoteURL: state.isRemote, + animation: $editedAnimation, + existingAnimation: state.existingAnimation + ) + .interactiveDismissDisabled(false) + .onChange(of: editedAnimation) { oldValue, newValue in + if let animation = newValue { + // Animation was successfully imported/edited via editor + if let existingAnim = editingExistingAnimation { + // Editing existing animation - update it + var animations = Defaults[.customIdleAnimations] + if let index = animations.firstIndex(where: { $0.id == existingAnim.id }) { + animations[index] = animation + Defaults[.customIdleAnimations] = animations + + // Update selection if this was the selected animation + if selectedIdleAnimation?.id == existingAnim.id { + Defaults[.selectedIdleAnimation] = animation } } - showingEditor = false - // Reset editor state - editorSourceURL = nil - editedAnimation = nil - editingExistingAnimation = nil + } else { + // New import - just select it + withAnimation { + selectedIdleAnimation = animation + } } + showingEditor = false + // Reset editor state + editorSourceURL = nil + editedAnimation = nil + editingExistingAnimation = nil } } } @@ -200,7 +221,12 @@ struct IdleAnimationsSettingsSection: View { editorIsRemote = false editedAnimation = nil editingExistingAnimation = nil // Reset to nil for new imports - showingEditor = true + + // Show editor with slight delay to ensure state is set + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + showingEditor = true + print("📥 [Import] Opening editor for new import") + } case .failure(let error): importError = error.localizedDescription @@ -221,7 +247,12 @@ struct IdleAnimationsSettingsSection: View { editedAnimation = nil editingExistingAnimation = nil // Reset to nil for new imports showingURLSheet = false - showingEditor = true + + // Show editor with slight delay to ensure state is set + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + showingEditor = true + print("📥 [Import] Opening editor for URL import") + } } } diff --git a/SHEET_PRESENTATION_FIX.md b/SHEET_PRESENTATION_FIX.md new file mode 100644 index 0000000..7da60a4 --- /dev/null +++ b/SHEET_PRESENTATION_FIX.md @@ -0,0 +1,163 @@ +# Sheet Presentation Fix + +## Problems Fixed + +### Issue 1: Editor Not Opening After App Restart ✅ +**Symptom**: After restarting the app, clicking "Edit Animation" showed logs but editor sheet didn't appear. + +**Root Cause**: The sheet was using `.sheet(isPresented: $showingEditor)` with an `if let url = editorSourceURL` wrapper inside. When state variables were updated, SwiftUI's sheet didn't properly re-evaluate the `if let` condition, causing the sheet content to be nil even though `isPresented` was true. + +**Fix**: Changed to item-based sheet presentation: +```swift +// Before (BROKEN) +.sheet(isPresented: $showingEditor) { + if let url = editorSourceURL { + AnimationEditorView(...) + } +} + +// After (WORKING) +.sheet(item: Binding( + get: { showingEditor ? EditorState(...) : nil }, + set: { newValue in showingEditor = (newValue != nil) } +)) { state in + AnimationEditorView(...) +} +``` + +This ensures the sheet content is always evaluated when `showingEditor` changes. + +### Issue 2: Imports Not Showing Editor ✅ +**Symptom**: When importing new animations, the editor didn't open, animations were imported with default transforms. + +**Root Cause**: Same as Issue 1 - the sheet state wasn't being properly synchronized. + +**Fix**: Added delays and logging: +```swift +DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + showingEditor = true + print("📥 [Import] Opening editor for new import") +} +``` + +### Issue 3: Edit Button Clicks Not Registering After Restart ✅ +**Symptom**: Logs showed edit was being triggered, but sheet didn't appear. + +**Root Cause**: State updates were happening synchronously, and SwiftUI sheet wasn't detecting the change. + +**Fix**: Added small delay before setting `showingEditor = true`: +```swift +DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + showingEditor = true + print("🔧 [Edit] Sheet should now be visible") +} +``` + +## Technical Details + +### EditorState Struct +Created a new identifiable struct to properly manage sheet state: +```swift +private struct EditorState: Identifiable { + let id = UUID() + let url: URL? + let isRemote: Bool + let existingAnimation: CustomIdleAnimation? +} +``` + +This makes the sheet's `item` parameter work correctly and ensures proper state management. + +### State Flow +1. User clicks "Edit" or imports file +2. State variables set: `editorSourceURL`, `editorIsRemote`, `editingExistingAnimation` +3. After 0.05-0.1s delay: `showingEditor = true` +4. Sheet's `item` getter evaluates and creates `EditorState` +5. Sheet presents with correct `AnimationEditorView` +6. On dismiss: Sheet's `item` setter cleans up state + +### Benefits of Item-Based Presentation +- **More reliable**: Item changes always trigger sheet updates +- **Better cleanup**: Setter can clean up state when sheet dismisses +- **Type safety**: Editor state is encapsulated in a struct +- **Debugging**: State changes are more explicit and trackable + +## Testing + +### Test 1: Edit After Restart +1. Launch app +2. Right-click any animation +3. Click "Edit Animation" +4. **Check console**: + ``` + 🔧 [Edit] Attempting to edit animation: Orange Cat Peeping + 🔧 [Edit] Animation source: lottieFile(...) + 🔧 [Edit] Lottie file URL: file://... + 🔧 [Edit] File exists: true + 🔧 [Edit] Sheet should now be visible + ``` +5. Editor should open ✅ + +### Test 2: Import New Animation +1. Click "+" button +2. Select a Lottie JSON file +3. **Check console**: + ``` + 📥 [Import] Opening editor for new import + ``` +4. Editor should open with preview and controls ✅ +5. Adjust transforms, save +6. Animation should appear in grid with transforms applied ✅ + +### Test 3: Import from URL +1. Click "+" → "From URL" +2. Enter URL and name +3. Click "Import" +4. **Check console**: + ``` + 📥 [Import] Opening editor for URL import + ``` +5. Editor should open ✅ + +### Test 4: Rapid Edits +1. Edit an animation +2. Save +3. Immediately edit again +4. Editor should open both times ✅ + +## Console Logs to Watch For + +### Successful Edit Flow: +``` +🔧 [Edit] Attempting to edit animation: [name] +🔧 [Edit] Animation source: [source type] +🔧 [Edit] Sheet should now be visible +✅ [AnimationEditor] Saved transform override for: [name] +📋 [CustomIdleAnimation] Found override for '[name]': AnimationTransformConfig(...) +🎨 [IdleAnimationView] Rendering animation: [name] +``` + +### Successful Import Flow: +``` +📥 [Import] Opening editor for new import +✅ [AnimationEditor] Imported animation: [name] +🎨 [IdleAnimationView] Rendering animation: [name] +``` + +## If Issues Persist + +1. **Check Console**: Look for the "Sheet should now be visible" message +2. **Verify State**: Add breakpoint in sheet's `item` getter to see if it's being called +3. **Check Delays**: If sheet still doesn't open, increase delay to 0.2s +4. **Clean Build**: Sometimes Xcode needs a clean build (Cmd+Shift+K) +5. **Check Logs**: All state changes should be logged now + +## Architecture Benefits + +The new item-based sheet presentation is more robust because: +- ✅ SwiftUI always evaluates item changes +- ✅ No nested `if let` inside sheet content +- ✅ State cleanup happens automatically in setter +- ✅ Better for async state updates +- ✅ More predictable behavior after app restarts +- ✅ Easier to debug with explicit state object