@imgly/editor-react-native 1.81.0-rc.0 → 1.81.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/android/build.gradle +4 -1
  2. package/package.json +1 -1
  3. package/src/NativeIMGLYEditor.ts +1 -1
  4. package/android/src/main/assets/ly.img.memories.style/content.json +0 -50
  5. package/android/src/main/assets/ly.img.memories.style/thumbnails/bubblegum.png +0 -0
  6. package/android/src/main/assets/ly.img.memories.style/thumbnails/hologram.png +0 -0
  7. package/android/src/main/assets/ly.img.memories.style/thumbnails/noir.png +0 -0
  8. package/android/src/main/assets/ly.img.memories.style/videos/bubblegum.mp4 +0 -0
  9. package/android/src/main/assets/ly.img.memories.style/videos/hologram.mp4 +0 -0
  10. package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesApp.kt +0 -77
  11. package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesConfiguration.kt +0 -38
  12. package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesEditor.kt +0 -79
  13. package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesViewModel.kt +0 -469
  14. package/android/src/main/kotlin/ly/img/editor/configuration/memories/callback/OnCreate.kt +0 -69
  15. package/android/src/main/kotlin/ly/img/editor/configuration/memories/callback/OnExport.kt +0 -46
  16. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/BottomPanel.kt +0 -151
  17. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/Dock.kt +0 -145
  18. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/ExportOverlay.kt +0 -229
  19. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/NavigationBar.kt +0 -80
  20. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/Overlay.kt +0 -138
  21. package/android/src/main/kotlin/ly/img/editor/configuration/memories/extension/EditorScopeExt.kt +0 -111
  22. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/AddPhotoAlternate.kt +0 -79
  23. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/ArrowForward.kt +0 -45
  24. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Audiotrack.kt +0 -43
  25. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Check.kt +0 -39
  26. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/CheckCircle.kt +0 -44
  27. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Close.kt +0 -43
  28. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Delete.kt +0 -54
  29. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/DeleteOutline.kt +0 -53
  30. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Fullscreen.kt +0 -74
  31. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Image.kt +0 -51
  32. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Info.kt +0 -48
  33. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Pause.kt +0 -46
  34. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/PlayArrow.kt +0 -38
  35. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/RepeatOff.kt +0 -80
  36. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/RepeatOn.kt +0 -59
  37. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/SkipPrevious.kt +0 -46
  38. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Sort.kt +0 -55
  39. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Style.kt +0 -59
  40. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Title.kt +0 -43
  41. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/TouchTriple.kt +0 -134
  42. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/VolumeDown.kt +0 -48
  43. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/VolumeUp.kt +0 -60
  44. package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/ExportStatus.kt +0 -20
  45. package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/ImageItem.kt +0 -8
  46. package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/TimelineImage.kt +0 -18
  47. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Events.kt +0 -59
  48. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Playback.kt +0 -29
  49. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/SceneSetup.kt +0 -79
  50. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Timeline.kt +0 -27
  51. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Title.kt +0 -162
  52. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/TrackEditor.kt +0 -740
  53. package/android/src/main/kotlin/ly/img/editor/configuration/memories/screen/ImageSelectionScreen.kt +0 -387
  54. package/android/src/main/kotlin/ly/img/editor/configuration/memories/screen/LoadingScreen.kt +0 -258
  55. package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/FilterStylesSheet.kt +0 -167
  56. package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/FilterStylesSheetType.kt +0 -21
  57. package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/VolumeSheet.kt +0 -104
  58. package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/StyleApplier.kt +0 -220
  59. package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/StyleAssetSource.kt +0 -43
  60. package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/VideoStyle.kt +0 -118
  61. package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Animations.kt +0 -58
  62. package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Constants.kt +0 -20
  63. package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/ImageDate.kt +0 -81
  64. package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Utils.kt +0 -27
  65. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/CenteredAddImageButton.kt +0 -82
  66. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/DraggableImageItem.kt +0 -145
  67. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/FullscreenVideoControls.kt +0 -136
  68. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageGrid.kt +0 -122
  69. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageGridOverlay.kt +0 -149
  70. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageTimelineStrip.kt +0 -323
  71. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageToolbar.kt +0 -401
  72. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ReorderableGridState.kt +0 -183
  73. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/VideoControlPanel.kt +0 -180
  74. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/VideoThumbnail.kt +0 -110
@@ -1,469 +0,0 @@
1
- package ly.img.editor.configuration.memories
2
-
3
- import android.content.Context
4
- import android.util.Log
5
- import androidx.lifecycle.ViewModel
6
- import androidx.lifecycle.viewModelScope
7
- import kotlinx.coroutines.Job
8
- import kotlinx.coroutines.delay
9
- import kotlinx.coroutines.flow.MutableStateFlow
10
- import kotlinx.coroutines.flow.StateFlow
11
- import kotlinx.coroutines.flow.asStateFlow
12
- import kotlinx.coroutines.launch
13
- import ly.img.editor.configuration.memories.model.ImageItem
14
- import ly.img.editor.configuration.memories.scene.applyDraftToTracks
15
- import ly.img.editor.configuration.memories.scene.findTracks
16
- import ly.img.editor.configuration.memories.scene.getPageVolume
17
- import ly.img.editor.configuration.memories.scene.readImagesFromTracks
18
- import ly.img.editor.configuration.memories.scene.setPageLooping
19
- import ly.img.editor.configuration.memories.scene.setPageVolume
20
- import ly.img.editor.configuration.memories.scene.togglePageLoop
21
- import ly.img.editor.configuration.memories.style.VideoStyles
22
- import ly.img.editor.configuration.memories.style.applyStyleToSlideshow
23
- import ly.img.editor.configuration.memories.util.detectSortOrder
24
- import ly.img.editor.configuration.memories.util.sortByDate
25
- import ly.img.editor.core.EditorContext
26
- import ly.img.editor.core.event.EditorEvent
27
- import ly.img.editor.core.state.EditorViewMode
28
-
29
- private const val TAG = "MemoriesVM"
30
-
31
- class MemoriesViewModel : ViewModel() {
32
- private var appContext: Context? = null
33
-
34
- fun setContext(context: Context) {
35
- appContext = context.applicationContext
36
- }
37
-
38
- fun getContext(): Context = appContext
39
- ?: throw IllegalStateException("Context not set in ViewModel")
40
-
41
- // Playback state
42
- private val _isPlaying = MutableStateFlow(false)
43
- val isPlaying: StateFlow<Boolean> = _isPlaying.asStateFlow()
44
-
45
- // Selected images for the video
46
- private val _selectedImages = MutableStateFlow<List<ImageItem>>(emptyList())
47
- val selectedImages: StateFlow<List<ImageItem>> = _selectedImages.asStateFlow()
48
-
49
- // Ids of images the user has marked for deletion (selection mode on the picker screen)
50
- private val _imagesToDelete = MutableStateFlow<Set<Int>>(emptySet())
51
- val imagesToDelete: StateFlow<Set<Int>> = _imagesToDelete.asStateFlow()
52
-
53
- // Multi-select mode: when on, tiles show checkboxes and delete removes the selection;
54
- // when off, delete clears all. Shared by the picker screen and the in-editor overlay.
55
- private val _multiSelectMode = MutableStateFlow(false)
56
- val multiSelectMode: StateFlow<Boolean> = _multiSelectMode.asStateFlow()
57
-
58
- // Image grid state (used in overlay during editing)
59
- private val _gridImages = MutableStateFlow<List<ImageItem>>(emptyList())
60
- val gridImages: StateFlow<List<ImageItem>> = _gridImages.asStateFlow()
61
-
62
- private val _showImageGrid = MutableStateFlow(false)
63
- val showImageGrid: StateFlow<Boolean> = _showImageGrid.asStateFlow()
64
-
65
- // Overlay (in-editor grid) staging state — edits are non-destructive until applied
66
- private val _overlaySortEnabled = MutableStateFlow(false)
67
- val overlaySortEnabled: StateFlow<Boolean> = _overlaySortEnabled.asStateFlow()
68
-
69
- private val _overlaySortAscending = MutableStateFlow(true)
70
- val overlaySortAscending: StateFlow<Boolean> = _overlaySortAscending.asStateFlow()
71
-
72
- // Last filter style applied to image blocks, re-applied after an in-place track rebuild
73
- private var activeFilter: String? = null
74
-
75
- /** The currently applied style id (Default until one is chosen), so the Styles sheet preselects it. */
76
- val activeStyleId: String get() = activeFilter ?: VideoStyles.DEFAULT.id
77
-
78
- // Picker thumbnails (style id -> thumbUri), read once from the custom style asset source.
79
- private val _styleThumbnails = MutableStateFlow<Map<String, String>>(emptyMap())
80
- val styleThumbnails: StateFlow<Map<String, String>> = _styleThumbnails.asStateFlow()
81
-
82
- /** Cache the style picker thumbnails resolved from the custom style asset source. */
83
- fun setStyleThumbnails(thumbnails: Map<String, String>) {
84
- _styleThumbnails.value = thumbnails
85
- }
86
-
87
- // Animation pairs
88
- private val _animationPairs =
89
- MutableStateFlow<List<ly.img.editor.configuration.memories.util.AnimationPair>>(emptyList())
90
- val animationPairs: StateFlow<List<ly.img.editor.configuration.memories.util.AnimationPair>> =
91
- _animationPairs.asStateFlow()
92
-
93
- // Video title
94
- private val _videoTitle = MutableStateFlow("")
95
- val videoTitle: StateFlow<String> = _videoTitle.asStateFlow()
96
-
97
- // Preview mode state management
98
- private val _isPreviewMode = MutableStateFlow(false)
99
- val isPreviewMode: StateFlow<Boolean> = _isPreviewMode.asStateFlow()
100
-
101
- // State to control when dock/bottomPanel should be hidden during preview transition
102
- private val _shouldHideUIForPreview = MutableStateFlow(false)
103
- val shouldHideUIForPreview: StateFlow<Boolean> = _shouldHideUIForPreview.asStateFlow()
104
-
105
- // Fullscreen mode state (for hiding system UI)
106
- private val _isFullscreen = MutableStateFlow(false)
107
- val isFullscreen: StateFlow<Boolean> = _isFullscreen.asStateFlow()
108
-
109
- // Editor loading state
110
- private val _isEditorLoading = MutableStateFlow(true)
111
- val isEditorLoading: StateFlow<Boolean> = _isEditorLoading.asStateFlow()
112
-
113
- // Loading start time for minimum duration calculation
114
- private val _loadingStartTime = MutableStateFlow(0L)
115
- val loadingStartTime: StateFlow<Long> = _loadingStartTime.asStateFlow()
116
-
117
- // Loop state management
118
- private val _isLooping = MutableStateFlow(true)
119
- val isLooping: StateFlow<Boolean> = _isLooping.asStateFlow()
120
-
121
- // Volume state management
122
- private val _volume = MutableStateFlow(1.0f)
123
- val volume: StateFlow<Float> = _volume.asStateFlow()
124
-
125
- // Sort functionality
126
- private val _isSortEnabled = MutableStateFlow(false)
127
- val isSortEnabled: StateFlow<Boolean> = _isSortEnabled.asStateFlow()
128
-
129
- private val _isSortAscending = MutableStateFlow(true)
130
- val isSortAscending: StateFlow<Boolean> = _isSortAscending.asStateFlow()
131
-
132
- // Editor context for direct editor control
133
- private var editorContext: EditorContext? = null
134
-
135
- // The in-flight "enter preview" transition, cancelled on the next toggle so a fast double-tap
136
- // can't apply a stale view-mode change after the UI state has flipped back.
137
- private var previewJob: Job? = null
138
-
139
- // Functions to update state
140
- fun setPlaying(playing: Boolean) {
141
- _isPlaying.value = playing
142
- }
143
-
144
- fun updateGridImages(images: List<ImageItem>) {
145
- _gridImages.value = images
146
- // Reflect the new order in the overlay sort button (a manual reorder turns it off).
147
- refreshOverlaySortState()
148
- }
149
-
150
- fun updateSelectedImages(images: List<ImageItem>) {
151
- _selectedImages.value = images
152
- // Also update grid images to match selected images initially
153
- _gridImages.value = images
154
- // Reflect the new order in the sort button: enabled (with direction) when the list is in
155
- // chronological order, off otherwise — so a manual reorder out of order turns it off.
156
- refreshSortState()
157
- }
158
-
159
- /**
160
- * Append newly-picked images to the selection. When sort is currently enabled the combined list
161
- * is re-sorted so the additions land in chronological order; otherwise they are appended as-is.
162
- * Either way [updateSelectedImages] then reconciles the sort button with the resulting order.
163
- */
164
- fun addSelectedImages(newImages: List<ImageItem>) {
165
- if (newImages.isEmpty()) return
166
- val combined = _selectedImages.value + newImages
167
- val next = if (_isSortEnabled.value) sortByDate(combined, _isSortAscending.value) else combined
168
- updateSelectedImages(next)
169
- }
170
-
171
- fun setAnimationPairs(pairs: List<ly.img.editor.configuration.memories.util.AnimationPair>) {
172
- _animationPairs.value = pairs
173
- }
174
-
175
- fun setVideoTitle(title: String) {
176
- _videoTitle.value = title
177
- }
178
-
179
- fun togglePreviewMode() {
180
- val currentPreviewMode = _isPreviewMode.value
181
- // Cancel any in-flight enter-preview transition before starting/reverting one.
182
- previewJob?.cancel()
183
- if (!currentPreviewMode) {
184
- // Entering preview mode: hide UI first, then enable preview after animation delay
185
- _shouldHideUIForPreview.value = true
186
- _isPreviewMode.value = true
187
- _isFullscreen.value = true // Hide system UI
188
-
189
- // Handle delayed preview mode transition in ViewModel scope
190
- previewJob = viewModelScope.launch {
191
- // Guarded like every other engine access here: the editor may be disposed inside the
192
- // 200ms window, so never let it throw uncaught in viewModelScope.
193
- runCatching {
194
- editorContext?.let { context ->
195
- context.engine.block.findAllSelected().forEach { selectedBlock ->
196
- context.engine.block.setSelected(selectedBlock, false)
197
- }
198
- context.eventHandler.send(EditorEvent.Sheet.Close(false))
199
- delay(200) // Animation duration for dock/bottomPanel
200
- // Only enter preview if we are still meant to be in it (the cancel above
201
- // usually covers a toggle-back, but re-check to be safe).
202
- if (_isPreviewMode.value) {
203
- context.eventHandler.send(EditorEvent.SetViewMode(EditorViewMode.Preview()))
204
- }
205
- }
206
- }.onFailure { Log.w(TAG, "Could not enter preview mode", it) }
207
- }
208
- } else {
209
- // Exiting preview mode: disable preview immediately, show UI after
210
- _isPreviewMode.value = false
211
- _shouldHideUIForPreview.value = false
212
- _isFullscreen.value = false // Show system UI
213
-
214
- // Set editor to edit mode immediately
215
- runCatching {
216
- editorContext?.eventHandler?.send(EditorEvent.SetViewMode(EditorViewMode.Edit()))
217
- }.onFailure { Log.w(TAG, "Could not exit preview mode", it) }
218
- }
219
- }
220
-
221
- fun setEditorLoading(loading: Boolean) {
222
- if (loading) {
223
- _loadingStartTime.value = System.currentTimeMillis()
224
- }
225
- _isEditorLoading.value = loading
226
- }
227
-
228
- fun toggleSort() {
229
- if (_isSortEnabled.value) {
230
- // Already in chronological order — flip the direction.
231
- _isSortAscending.value = !_isSortAscending.value
232
- } else {
233
- // List is out of order (e.g. after a manual drag): restore the last direction first
234
- // rather than jumping to the opposite one. A second tap then flips it.
235
- _isSortEnabled.value = true
236
- }
237
- applySortToImages()
238
- }
239
-
240
- fun clearSelectedImages() {
241
- _selectedImages.value = emptyList()
242
- _gridImages.value = emptyList()
243
- _imagesToDelete.value = emptySet()
244
- _isSortEnabled.value = false
245
- }
246
-
247
- /** Toggle multi-select mode; either transition resets the current selection to empty. */
248
- fun toggleMultiSelect() {
249
- _multiSelectMode.value = !_multiSelectMode.value
250
- _imagesToDelete.value = emptySet()
251
- }
252
-
253
- /** Toggle whether an image (by id) is marked for deletion. */
254
- fun toggleImageForDeletion(id: Int) {
255
- _imagesToDelete.value = _imagesToDelete.value.toMutableSet().apply {
256
- if (!add(id)) remove(id)
257
- }
258
- }
259
-
260
- /** Remove only the images currently marked for deletion. */
261
- fun deleteSelectedImages() {
262
- val toDelete = _imagesToDelete.value
263
- if (toDelete.isEmpty()) return
264
- val remaining = _selectedImages.value.filterNot { it.id in toDelete }
265
- _selectedImages.value = remaining
266
- _gridImages.value = remaining
267
- _imagesToDelete.value = emptySet()
268
- // Removing images can leave (or break) chronological order — keep the button in sync.
269
- refreshSortState()
270
- }
271
-
272
- // ---- In-editor overlay grid: non-destructive staging ---------------------------------------
273
-
274
- // ──────────────── In-editor overlay (non-destructive staging) ────────────────
275
-
276
- /** Open the overlay seeded from the tracks (the source of truth) and pause playback. */
277
- fun openImageGridFromTracks(editorContext: EditorContext) {
278
- val engine = editorContext.engine
279
- val page = engine.scene.getCurrentPage() ?: return
280
- val tracks = findTracks(engine, page) ?: return
281
- _gridImages.value = try {
282
- readImagesFromTracks(engine, tracks)
283
- } catch (e: Exception) {
284
- Log.e(TAG, "Error reading images from tracks", e)
285
- _gridImages.value
286
- }
287
- _imagesToDelete.value = emptySet()
288
- _multiSelectMode.value = false
289
- // Reflect the existing track order: if the images are already chronological, show it.
290
- _overlaySortAscending.value = true
291
- refreshOverlaySortState()
292
- _showImageGrid.value = true
293
- try {
294
- engine.scene.getCurrentPage()?.let {
295
- engine.block.setPlaying(it, false)
296
- _isPlaying.value = false
297
- }
298
- } catch (e: Exception) {
299
- Log.e(TAG, "Error pausing playback when opening overlay", e)
300
- }
301
- }
302
-
303
- /** Close the overlay discarding the staged edits (tracks are untouched). */
304
- fun cancelOverlay() {
305
- _showImageGrid.value = false
306
- _imagesToDelete.value = emptySet()
307
- _multiSelectMode.value = false
308
- }
309
-
310
- /** Sort the staged overlay draft by creation date (does not touch the tracks until applied). */
311
- fun toggleOverlaySort() {
312
- if (_overlaySortEnabled.value) {
313
- // Already in chronological order — flip the direction.
314
- _overlaySortAscending.value = !_overlaySortAscending.value
315
- } else {
316
- // Out of order (e.g. after a manual drag): restore the last direction first.
317
- _overlaySortEnabled.value = true
318
- }
319
- _gridImages.value = sortByDate(_gridImages.value, _overlaySortAscending.value)
320
- }
321
-
322
- /** Append newly-picked images to the draft. */
323
- fun addImagesToOverlay(images: List<ImageItem>) {
324
- if (images.isEmpty()) return
325
- val combined = _gridImages.value + images
326
- // Keep chronological order when sort is on; otherwise just append.
327
- _gridImages.value =
328
- if (_overlaySortEnabled.value) sortByDate(combined, _overlaySortAscending.value) else combined
329
- refreshOverlaySortState()
330
- }
331
-
332
- /** Remove the overlay-selected images from the draft only. */
333
- fun deleteSelectedFromOverlay() {
334
- val toDelete = _imagesToDelete.value
335
- if (toDelete.isEmpty()) return
336
- _gridImages.value = _gridImages.value.filterNot { it.id in toDelete }
337
- _imagesToDelete.value = emptySet()
338
- // Removing images can leave (or break) chronological order — keep the button in sync.
339
- refreshOverlaySortState()
340
- }
341
-
342
- /** Clear all images from the draft only. */
343
- fun clearOverlayImages() {
344
- _gridImages.value = emptyList()
345
- _imagesToDelete.value = emptySet()
346
- }
347
-
348
- /** Apply the staged draft to the tracks in-place, then close the overlay. */
349
- fun applyOverlayChanges(editorContext: EditorContext) {
350
- val engine = editorContext.engine
351
- viewModelScope.launch {
352
- try {
353
- val page = engine.scene.getCurrentPage() ?: return@launch
354
- val tracks = findTracks(engine, page) ?: return@launch
355
- val draft = _gridImages.value
356
- applyDraftToTracks(
357
- engine = engine,
358
- page = page,
359
- tracks = tracks,
360
- staged = draft,
361
- animationPairs = _animationPairs.value,
362
- hasTitle = _videoTitle.value.isNotEmpty(),
363
- )
364
- // New blocks default to full-bleed and no effects; re-apply the active style so they
365
- // match (including Default, which resets scale to full-bleed and clears the backdrop).
366
- applyStyle(editorContext, activeStyleId)
367
- _selectedImages.value = draft
368
- _imagesToDelete.value = emptySet()
369
- _multiSelectMode.value = false
370
- _showImageGrid.value = false
371
- } catch (e: Exception) {
372
- Log.e(TAG, "Error applying overlay changes", e)
373
- }
374
- }
375
- }
376
-
377
- private fun applySortToImages() {
378
- if (_isSortEnabled.value) {
379
- val sortedImages = sortByDate(_selectedImages.value, _isSortAscending.value)
380
- _selectedImages.value = sortedImages
381
- _gridImages.value = sortedImages
382
- }
383
- }
384
-
385
- /**
386
- * Reconcile the picker's sort state with the current order of [_selectedImages]. When the list
387
- * happens to be in chronological order the sort button reflects that direction; when it is out
388
- * of order sort turns off but the last direction is remembered (so the next tap restores it).
389
- */
390
- private fun refreshSortState() {
391
- val order = detectSortOrder(_selectedImages.value)
392
- if (order == null) {
393
- _isSortEnabled.value = false // keep _isSortAscending so the next tap restores this direction
394
- } else {
395
- _isSortEnabled.value = true
396
- _isSortAscending.value = order
397
- }
398
- }
399
-
400
- /** Same reconciliation as [refreshSortState] but for the in-editor overlay draft. */
401
- private fun refreshOverlaySortState() {
402
- val order = detectSortOrder(_gridImages.value)
403
- if (order == null) {
404
- _overlaySortEnabled.value = false
405
- } else {
406
- _overlaySortEnabled.value = true
407
- _overlaySortAscending.value = order
408
- }
409
- }
410
-
411
- fun setEditorContext(context: EditorContext) {
412
- editorContext = context
413
- }
414
-
415
- /** Drop the editor context when the editor closes, so post-close calls no-op instead of acting
416
- * on a stale/disposed editor. */
417
- fun clearEditorContext() {
418
- previewJob?.cancel()
419
- editorContext = null
420
- }
421
-
422
- /** Loop on, volume synced — called once the scene is built. */
423
- fun initializeEditorState() {
424
- val engine = editorContext?.engine ?: return
425
- viewModelScope.launch {
426
- runCatching {
427
- setPageLooping(engine, true)
428
- _isLooping.value = true
429
- getPageVolume(engine)?.let { _volume.value = it }
430
- }.onFailure { Log.w(TAG, "Could not initialize editor state", it) }
431
- }
432
- }
433
-
434
- // ──────────────── Playback: loop & volume ────────────────
435
-
436
- fun toggleLoop() {
437
- val engine = editorContext?.engine ?: return
438
- viewModelScope.launch {
439
- runCatching { togglePageLoop(engine)?.let { _isLooping.value = it } }
440
- .onFailure { Log.w(TAG, "Could not toggle loop", it) }
441
- }
442
- }
443
-
444
- fun setVolume(volume: Float) {
445
- val engine = editorContext?.engine ?: return
446
- viewModelScope.launch {
447
- runCatching {
448
- setPageVolume(engine, volume)
449
- _volume.value = volume
450
- }.onFailure { Log.w(TAG, "Could not set volume", it) }
451
- }
452
- }
453
-
454
- // ──────────────── Styles ────────────────
455
-
456
- /** Apply a style to the slideshow (see [applyStyleToSlideshow]); remembered for re-applies. */
457
- fun applyStyle(
458
- editorContext: EditorContext,
459
- styleId: String,
460
- ) {
461
- activeFilter = styleId
462
- val engine = editorContext.engine
463
- viewModelScope.launch {
464
- val page = engine.scene.getCurrentPage() ?: return@launch
465
- runCatching { applyStyleToSlideshow(engine, page, VideoStyles.byId(styleId)) }
466
- .onFailure { Log.w(TAG, "Could not apply style '$styleId'", it) }
467
- }
468
- }
469
- }
@@ -1,69 +0,0 @@
1
- package ly.img.editor.configuration.memories.callback
2
-
3
- import androidx.compose.ui.unit.dp
4
- import kotlinx.coroutines.flow.first
5
- import ly.img.editor.configuration.memories.MemoriesConfiguration
6
- import ly.img.editor.configuration.memories.MemoriesViewModel
7
- import ly.img.editor.configuration.memories.extension.onLoadAssetSources
8
- import ly.img.editor.configuration.memories.scene.calculateTotalDuration
9
- import ly.img.editor.configuration.memories.scene.createBurstImages
10
- import ly.img.editor.configuration.memories.scene.createMainImageSequence
11
- import ly.img.editor.configuration.memories.scene.createSlideshowScene
12
- import ly.img.editor.configuration.memories.scene.createTitleBlock
13
- import ly.img.editor.configuration.memories.scene.setupEventSubscriptions
14
- import ly.img.editor.configuration.memories.scene.setupTracks
15
- import ly.img.editor.configuration.memories.style.loadStyleThumbnails
16
- import ly.img.editor.configuration.memories.util.Animations
17
- import ly.img.editor.configuration.memories.util.CANVAS_PADDING_DP
18
- import ly.img.editor.core.EditorScope
19
- import ly.img.editor.core.component.data.Insets
20
- import ly.img.editor.core.event.EditorEvent
21
-
22
- private fun EditorScope.applyCanvasPadding() {
23
- editorContext.eventHandler.send(EditorEvent.Insets.SetExtra(insets = Insets(CANVAS_PADDING_DP.dp)))
24
- }
25
-
26
- fun MemoriesConfiguration.onCreateConfiguration(viewModel: MemoriesViewModel): (suspend EditorScope.() -> Unit) = onCreate@{
27
- val engine = editorContext.engine
28
- showLoading = true
29
- viewModel.setEditorContext(editorContext)
30
-
31
- val page = createSlideshowScene(engine)
32
- onLoadAssetSources()
33
- // Resolve the style picker thumbnails from the custom asset source now that it is registered.
34
- viewModel.setStyleThumbnails(engine.loadStyleThumbnails())
35
-
36
- // Without this the engine flattens our multi-track crossfade timeline on snapshot-restore
37
- // (e.g. undo): it strips "foreground" tracks and reparents their clips to the page. Must be
38
- // set after the scene exists.
39
- engine.editor.setSettingBoolean("features/removeForegroundTracksOnSceneLoad", false)
40
-
41
- val tracks = setupTracks(engine, page)
42
-
43
- val animationPairs = Animations.createAnimationPairs()
44
- viewModel.setAnimationPairs(animationPairs)
45
-
46
- val images = viewModel.gridImages.first()
47
- val hasTitle = createTitleBlock(engine, page, viewModel.videoTitle.first(), tracks.textTrack)
48
- createBurstImages(engine, page, runCatching { viewModel.getContext() }.getOrNull(), images, hasTitle)
49
-
50
- // One track per slide, positioned by its time offset (which already includes the title
51
- // offset when a title is present), each appended on top of the previous for the crossfade.
52
- // Returns each slot's on-timeline duration (a short video is only as long as its source).
53
- val durations = createMainImageSequence(engine, page, images, hasTitle, animationPairs)
54
-
55
- val totalDuration = calculateTotalDuration(images, durations, hasTitle)
56
- engine.block.setDuration(page, duration = totalDuration)
57
- engine.block.setDuration(tracks.backgroundBlock, duration = totalDuration)
58
- engine.block.setDuration(tracks.matteBlock, duration = totalDuration)
59
-
60
- engine.block.fillParent(tracks.backgroundTrack)
61
- engine.block.fillParent(tracks.matteTrack)
62
- engine.block.fillParent(tracks.textTrack)
63
-
64
- viewModel.initializeEditorState()
65
- showLoading = false
66
-
67
- setupEventSubscriptions(engine, editorContext, viewModel)
68
- applyCanvasPadding()
69
- }
@@ -1,46 +0,0 @@
1
- package ly.img.editor.configuration.memories.callback
2
-
3
- import kotlinx.coroutines.CancellationException
4
- import ly.img.editor.configuration.memories.MemoriesConfiguration
5
- import ly.img.editor.configuration.memories.model.ExportStatus
6
- import ly.img.engine.MimeType
7
- import java.nio.ByteBuffer
8
-
9
- /**
10
- * Render the slideshow page to an MP4, reporting progress through [MemoriesConfiguration.exportStatus]
11
- * so the overlay can show a progress circle, then a share button on success.
12
- */
13
- suspend fun MemoriesConfiguration.onExport() {
14
- try {
15
- exportStatus = ExportStatus.Loading(progress = 0f)
16
- val buffer = exportSlideshow()
17
- val file = writeToFile(byteBuffer = buffer, mimeType = MimeType.MP4)
18
- exportStatus = ExportStatus.Success(file = file, mimeType = MimeType.MP4)
19
- } catch (cancellation: CancellationException) {
20
- exportStatus = null
21
- throw cancellation
22
- } catch (exception: Exception) {
23
- exportStatus = ExportStatus.Error(exception)
24
- }
25
- }
26
-
27
- private suspend fun MemoriesConfiguration.exportSlideshow(): ByteBuffer {
28
- val engine = editorContext.engine
29
- val page = requireNotNull(engine.scene.getCurrentPage())
30
- return engine.block.exportVideo(
31
- block = page,
32
- timeOffset = 0.0,
33
- duration = engine.block.getDuration(page),
34
- mimeType = MimeType.MP4,
35
- progressCallback = { progress ->
36
- if (progress.totalFrames > 0) {
37
- val fraction = progress.encodedFrames.toFloat() / progress.totalFrames
38
- val current = exportStatus
39
- // Only push a new state on a visible (~1%) change to avoid churning recomposition.
40
- if (current !is ExportStatus.Loading || fraction >= current.progress + 0.01f) {
41
- exportStatus = ExportStatus.Loading(progress = fraction)
42
- }
43
- }
44
- },
45
- )
46
- }