@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,387 +0,0 @@
1
- package ly.img.editor.configuration.memories.screen
2
-
3
- import android.net.Uri
4
- import androidx.activity.compose.BackHandler
5
- import androidx.activity.compose.rememberLauncherForActivityResult
6
- import androidx.activity.result.PickVisualMediaRequest
7
- import androidx.activity.result.contract.ActivityResultContracts
8
- import androidx.compose.foundation.background
9
- import androidx.compose.foundation.layout.Arrangement
10
- import androidx.compose.foundation.layout.Box
11
- import androidx.compose.foundation.layout.Column
12
- import androidx.compose.foundation.layout.PaddingValues
13
- import androidx.compose.foundation.layout.Row
14
- import androidx.compose.foundation.layout.Spacer
15
- import androidx.compose.foundation.layout.defaultMinSize
16
- import androidx.compose.foundation.layout.fillMaxSize
17
- import androidx.compose.foundation.layout.fillMaxWidth
18
- import androidx.compose.foundation.layout.height
19
- import androidx.compose.foundation.layout.navigationBarsPadding
20
- import androidx.compose.foundation.layout.padding
21
- import androidx.compose.foundation.lazy.grid.rememberLazyGridState
22
- import androidx.compose.foundation.shape.RoundedCornerShape
23
- import androidx.compose.foundation.text.KeyboardOptions
24
- import androidx.compose.material3.ExperimentalMaterial3Api
25
- import androidx.compose.material3.Icon
26
- import androidx.compose.material3.MaterialTheme
27
- import androidx.compose.material3.OutlinedTextField
28
- import androidx.compose.material3.Text
29
- import androidx.compose.runtime.Composable
30
- import androidx.compose.runtime.collectAsState
31
- import androidx.compose.runtime.derivedStateOf
32
- import androidx.compose.runtime.getValue
33
- import androidx.compose.runtime.mutableFloatStateOf
34
- import androidx.compose.runtime.remember
35
- import androidx.compose.runtime.rememberCoroutineScope
36
- import androidx.compose.runtime.setValue
37
- import androidx.compose.ui.Alignment
38
- import androidx.compose.ui.Modifier
39
- import androidx.compose.ui.graphics.Brush
40
- import androidx.compose.ui.graphics.graphicsLayer
41
- import androidx.compose.ui.layout.onGloballyPositioned
42
- import androidx.compose.ui.platform.LocalContext
43
- import androidx.compose.ui.platform.LocalDensity
44
- import androidx.compose.ui.text.font.FontWeight
45
- import androidx.compose.ui.text.input.ImeAction
46
- import androidx.compose.ui.text.style.TextAlign
47
- import androidx.compose.ui.tooling.preview.Preview
48
- import androidx.compose.ui.unit.dp
49
- import androidx.compose.ui.unit.sp
50
- import androidx.compose.ui.zIndex
51
- import kotlinx.coroutines.launch
52
- import ly.img.editor.configuration.memories.MemoriesViewModel
53
- import ly.img.editor.configuration.memories.iconPack.Info
54
- import ly.img.editor.configuration.memories.model.ImageItem
55
- import ly.img.editor.configuration.memories.util.buildImageItems
56
- import ly.img.editor.configuration.memories.widget.CenteredAddImageButton
57
- import ly.img.editor.configuration.memories.widget.ImageGrid
58
- import ly.img.editor.configuration.memories.widget.ImageToolbar
59
- import ly.img.editor.core.iconpack.IconPack
60
-
61
- @OptIn(ExperimentalMaterial3Api::class)
62
- @Composable
63
- fun ImageSelectionScreen(
64
- viewModel: MemoriesViewModel,
65
- onProceedToEditor: () -> Unit,
66
- modifier: Modifier = Modifier,
67
- ) {
68
- val selectedImages by viewModel.selectedImages.collectAsState()
69
- val imagesToDelete by viewModel.imagesToDelete.collectAsState()
70
- val multiSelectMode by viewModel.multiSelectMode.collectAsState()
71
- val videoTitle by viewModel.videoTitle.collectAsState()
72
- val isSortEnabled by viewModel.isSortEnabled.collectAsState()
73
- val isSortAscending by viewModel.isSortAscending.collectAsState()
74
- val context = LocalContext.current
75
- val density = LocalDensity.current
76
- val scope = rememberCoroutineScope()
77
-
78
- // Back exits selection mode (rather than leaving the screen) while it's on.
79
- BackHandler(enabled = multiSelectMode) {
80
- viewModel.toggleMultiSelect()
81
- }
82
-
83
- // LazyGrid state for scroll monitoring
84
- val lazyGridState = rememberLazyGridState()
85
-
86
- // Height measurement for top content
87
- var columnHeight by remember { mutableFloatStateOf(0f) }
88
-
89
- // Calculate fade effect based on scroll position
90
- val fadeAlpha = remember {
91
- derivedStateOf {
92
- if (lazyGridState.firstVisibleItemIndex == 0) {
93
- val offset = lazyGridState.firstVisibleItemScrollOffset
94
- val fadeThreshold = with(density) { 100.dp.toPx() }
95
- (1f - (offset / fadeThreshold)).coerceIn(0f, 1f)
96
- } else {
97
- 0f
98
- }
99
- }
100
- }
101
-
102
- // Media picker launcher (images and videos)
103
- val photoPickerLauncher = rememberLauncherForActivityResult(
104
- contract = ActivityResultContracts.PickMultipleVisualMedia(),
105
- ) { uris: List<Uri> ->
106
- if (uris.isNotEmpty()) {
107
- // Base new ids on the current max id (not list size) so they stay unique
108
- // even after some images have been deleted.
109
- val baseId = selectedImages.maxOfOrNull { it.id } ?: 0
110
- scope.launch {
111
- val newImages = buildImageItems(context.contentResolver, uris, baseId)
112
- // The ViewModel merges these in (re-sorting if sort is on) and reconciles the
113
- // sort button: if the resulting list is chronological it flips sort on automatically.
114
- viewModel.addSelectedImages(newImages)
115
- }
116
- }
117
- }
118
-
119
- Box(
120
- modifier = modifier,
121
- ) {
122
- Column(
123
- modifier = Modifier
124
- .zIndex(if (fadeAlpha.value > .9f) 100f else -1f)
125
- .fillMaxWidth()
126
- .padding(16.dp)
127
- .onGloballyPositioned { coordinates ->
128
- columnHeight = coordinates.size.height.toFloat()
129
- }
130
- .graphicsLayer {
131
- alpha = fadeAlpha.value
132
- translationY = (1f - fadeAlpha.value) * -100f
133
- },
134
- verticalArrangement = Arrangement.spacedBy(2.dp),
135
- ) {
136
- // Title with a standalone informational icon to its right (its hint is exposed via
137
- // contentDescription for accessibility).
138
- Row(
139
- modifier = Modifier
140
- .defaultMinSize(minHeight = 48.dp)
141
- .fillMaxWidth(),
142
- verticalAlignment = Alignment.CenterVertically,
143
- ) {
144
- Text(
145
- text = "Create Your Video Memory",
146
- fontSize = 24.sp,
147
- fontWeight = FontWeight.Bold,
148
- textAlign = TextAlign.Start,
149
- modifier = Modifier.weight(1f),
150
- )
151
-
152
- // Only show the drag/select hint once there are images to act on
153
- if (selectedImages.isNotEmpty()) {
154
- Icon(
155
- imageVector = IconPack.Info,
156
- contentDescription = "Drag and drop images by long pressing; tap to select for deletion.",
157
- tint = MaterialTheme.colorScheme.onSurfaceVariant,
158
- modifier = Modifier.padding(12.dp),
159
- )
160
- }
161
- }
162
-
163
- Spacer(Modifier.height(8.dp))
164
-
165
- // Video title input field
166
- OutlinedTextField(
167
- value = videoTitle,
168
- onValueChange = { viewModel.setVideoTitle(it) },
169
- label = { Text("Add a title") },
170
- placeholder = { Text("My Amazing Memory") },
171
- modifier = Modifier.fillMaxWidth(),
172
- singleLine = true,
173
- keyboardOptions = KeyboardOptions(
174
- imeAction = ImeAction.Done,
175
- ),
176
- shape = RoundedCornerShape(8.dp),
177
- )
178
- }
179
-
180
- // Image Grid
181
- Box(
182
- modifier = Modifier
183
- .fillMaxSize(),
184
- ) {
185
- if (selectedImages.isEmpty()) {
186
- // Empty state: launch the device media picker
187
- CenteredAddImageButton(
188
- modifier = Modifier.align(Alignment.TopCenter),
189
- onClick = { photoPickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) },
190
- columnHeight = columnHeight,
191
- )
192
- } else {
193
- ImageGrid(
194
- modifier = Modifier
195
- .align(Alignment.TopCenter),
196
- images = selectedImages,
197
- onImagesChanged = { viewModel.updateSelectedImages(it) },
198
- contentPadding = PaddingValues(
199
- top = with(density) { (columnHeight / density.density).dp + 32.dp },
200
- bottom = 96.dp,
201
- start = 16.dp,
202
- end = 16.dp,
203
- ),
204
- lazyGridState = lazyGridState,
205
- selectedForDeletion = if (multiSelectMode) imagesToDelete else emptySet(),
206
- onToggleSelection = if (multiSelectMode) {
207
- { viewModel.toggleImageForDeletion(it) }
208
- } else {
209
- null
210
- },
211
- )
212
- }
213
-
214
- Box(
215
- modifier = Modifier
216
- .fillMaxWidth()
217
- .align(Alignment.TopCenter)
218
- .height(12.dp)
219
- .background(
220
- brush = Brush.verticalGradient(
221
- colors = listOf(
222
- MaterialTheme.colorScheme.background,
223
- MaterialTheme.colorScheme.background.copy(alpha = 0f),
224
- ),
225
- ),
226
- ),
227
- )
228
-
229
- Box(
230
- modifier = Modifier
231
- .fillMaxWidth()
232
- .align(Alignment.BottomCenter)
233
- .height(64.dp)
234
- .background(
235
- brush = Brush.verticalGradient(
236
- colors = listOf(
237
- MaterialTheme.colorScheme.background.copy(alpha = 0f),
238
- MaterialTheme.colorScheme.background,
239
- ),
240
- ),
241
- ),
242
- )
243
- }
244
-
245
- // Bottom toolbar - only show if there are images
246
- if (selectedImages.isNotEmpty()) {
247
- ImageToolbar(
248
- modifier = Modifier
249
- .align(Alignment.BottomCenter)
250
- .padding(horizontal = 16.dp)
251
- .navigationBarsPadding(),
252
- onAddImage = { photoPickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) },
253
- onToggleSort = { viewModel.toggleSort() },
254
- onDeleteAll = { viewModel.clearSelectedImages() },
255
- onDeleteSelected = { viewModel.deleteSelectedImages() },
256
- onProceed = onProceedToEditor,
257
- isSortEnabled = isSortEnabled,
258
- isSortAscending = isSortAscending,
259
- hasImages = selectedImages.isNotEmpty(),
260
- showDeleteButton = true,
261
- selectedForDeletionCount = imagesToDelete.size,
262
- totalCount = selectedImages.size,
263
- multiSelectMode = multiSelectMode,
264
- onToggleMultiSelect = { viewModel.toggleMultiSelect() },
265
- )
266
- }
267
- }
268
- }
269
-
270
- @Composable
271
- private fun createMockViewModel(
272
- selectedImages: List<ImageItem> = emptyList(),
273
- videoTitle: String = "",
274
- ): MemoriesViewModel {
275
- val viewModel = remember { MemoriesViewModel() }
276
-
277
- // Set the initial state synchronously in the preview
278
- viewModel.updateSelectedImages(selectedImages)
279
- if (videoTitle.isNotEmpty()) {
280
- viewModel.setVideoTitle(videoTitle)
281
- }
282
-
283
- return viewModel
284
- }
285
-
286
- @Preview(
287
- name = "Empty State",
288
- showBackground = true,
289
- )
290
- @Composable
291
- fun ImageSelectionScreen_EmptyState_Preview() {
292
- val mockViewModel = createMockViewModel()
293
-
294
- ImageSelectionScreen(
295
- viewModel = mockViewModel,
296
- onProceedToEditor = { },
297
- )
298
- }
299
-
300
- @Preview(
301
- name = "Unfocused with Sample Images",
302
- showBackground = true,
303
- )
304
- @Composable
305
- fun ImageSelectionScreen_WithImages_Preview() {
306
- val sampleImages = listOf(
307
- ImageItem(id = 1, url = ""),
308
- ImageItem(id = 2, url = ""),
309
- ImageItem(id = 3, url = ""),
310
- )
311
- val mockViewModel = createMockViewModel(selectedImages = sampleImages)
312
-
313
- ImageSelectionScreen(
314
- viewModel = mockViewModel,
315
- onProceedToEditor = { },
316
- )
317
- }
318
-
319
- @Preview(
320
- name = "Focused Text Input",
321
- showBackground = true,
322
- )
323
- @Composable
324
- fun ImageSelectionScreen_FocusedInput_Preview() {
325
- val sampleImages = listOf(
326
- ImageItem(id = 1, url = ""),
327
- ImageItem(id = 2, url = ""),
328
- )
329
- val mockViewModel = createMockViewModel(
330
- selectedImages = sampleImages,
331
- videoTitle = "My Amazing Memory",
332
- )
333
-
334
- ImageSelectionScreen(
335
- viewModel = mockViewModel,
336
- onProceedToEditor = { },
337
- )
338
- }
339
-
340
- @Preview(
341
- name = "User Photos with Many Images",
342
- showBackground = true,
343
- )
344
- @Composable
345
- fun ImageSelectionScreen_ManyUserPhotos_Preview() {
346
- val sampleImages = listOf(
347
- ImageItem(
348
- id = 1,
349
- url = "",
350
- creationDate = System.currentTimeMillis() - 86400000,
351
- ),
352
- ImageItem(
353
- id = 2,
354
- url = "",
355
- creationDate = System.currentTimeMillis() - 172800000,
356
- ),
357
- ImageItem(
358
- id = 3,
359
- url = "",
360
- creationDate = System.currentTimeMillis() - 259200000,
361
- ),
362
- ImageItem(
363
- id = 4,
364
- url = "",
365
- creationDate = System.currentTimeMillis() - 345600000,
366
- ),
367
- ImageItem(
368
- id = 5,
369
- url = "",
370
- creationDate = System.currentTimeMillis() - 432000000,
371
- ),
372
- ImageItem(
373
- id = 6,
374
- url = "",
375
- creationDate = System.currentTimeMillis() - 518400000,
376
- ),
377
- )
378
- val mockViewModel = createMockViewModel(
379
- selectedImages = sampleImages,
380
- videoTitle = "Summer Vacation 2024",
381
- )
382
-
383
- ImageSelectionScreen(
384
- viewModel = mockViewModel,
385
- onProceedToEditor = { },
386
- )
387
- }
@@ -1,258 +0,0 @@
1
- package ly.img.editor.configuration.memories.screen
2
-
3
- import androidx.compose.animation.core.animateFloatAsState
4
- import androidx.compose.animation.core.spring
5
- import androidx.compose.animation.core.tween
6
- import androidx.compose.foundation.background
7
- import androidx.compose.foundation.isSystemInDarkTheme
8
- import androidx.compose.foundation.layout.Arrangement
9
- import androidx.compose.foundation.layout.Box
10
- import androidx.compose.foundation.layout.Column
11
- import androidx.compose.foundation.layout.aspectRatio
12
- import androidx.compose.foundation.layout.fillMaxSize
13
- import androidx.compose.foundation.layout.padding
14
- import androidx.compose.foundation.lazy.grid.GridCells
15
- import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
16
- import androidx.compose.foundation.lazy.grid.items
17
- import androidx.compose.foundation.shape.RoundedCornerShape
18
- import androidx.compose.material3.MaterialTheme
19
- import androidx.compose.material3.Text
20
- import androidx.compose.runtime.Composable
21
- import androidx.compose.runtime.LaunchedEffect
22
- import androidx.compose.runtime.getValue
23
- import androidx.compose.runtime.mutableStateOf
24
- import androidx.compose.runtime.remember
25
- import androidx.compose.runtime.setValue
26
- import androidx.compose.ui.Alignment
27
- import androidx.compose.ui.Modifier
28
- import androidx.compose.ui.draw.alpha
29
- import androidx.compose.ui.draw.clip
30
- import androidx.compose.ui.draw.drawBehind
31
- import androidx.compose.ui.draw.scale
32
- import androidx.compose.ui.graphics.Brush
33
- import androidx.compose.ui.graphics.Color
34
- import androidx.compose.ui.graphics.drawscope.scale
35
- import androidx.compose.ui.input.pointer.pointerInput
36
- import androidx.compose.ui.layout.ContentScale
37
- import androidx.compose.ui.text.font.FontWeight
38
- import androidx.compose.ui.text.style.TextAlign
39
- import androidx.compose.ui.tooling.preview.Preview
40
- import androidx.compose.ui.tooling.preview.PreviewLightDark
41
- import androidx.compose.ui.unit.dp
42
- import coil.compose.AsyncImage
43
- import kotlinx.coroutines.delay
44
- import kotlinx.coroutines.launch
45
- import ly.img.editor.configuration.memories.model.ImageItem
46
- import ly.img.editor.core.theme.EditorTheme
47
- import kotlin.random.Random
48
-
49
- @Composable
50
- fun LoadingScreen(
51
- selectedImages: List<ImageItem>,
52
- modifier: Modifier = Modifier,
53
- ) {
54
- // Create a 4x4 grid (16 items)
55
- val gridSize = 16
56
- var flashingStates by remember { mutableStateOf(List(gridSize) { Random.nextBoolean() }) }
57
- var currentImages by remember { mutableStateOf<List<ImageItem?>>(List(gridSize) { null }) }
58
-
59
- // Staggered entrance: everything starts hidden, then the grid, title, and
60
- // description fade in one after another. Each fade has the same duration but a
61
- // later start (delayMillis), so they overlap instead of waiting for the
62
- // previous one to finish.
63
- var entered by remember { mutableStateOf(false) }
64
- LaunchedEffect(Unit) { entered = true }
65
-
66
- val entranceDuration = 600
67
- val entranceStagger = 300
68
- val gridAlpha by animateFloatAsState(
69
- targetValue = if (entered) 1f else 0f,
70
- animationSpec = tween(durationMillis = entranceDuration, delayMillis = 0),
71
- label = "grid_entrance",
72
- )
73
- val titleAlpha by animateFloatAsState(
74
- targetValue = if (entered) 1f else 0f,
75
- animationSpec = tween(durationMillis = entranceDuration, delayMillis = entranceStagger),
76
- label = "title_entrance",
77
- )
78
- val descriptionAlpha by animateFloatAsState(
79
- targetValue = if (entered) 1f else 0f,
80
- animationSpec = tween(durationMillis = entranceDuration, delayMillis = entranceStagger * 2),
81
- label = "description_entrance",
82
- )
83
-
84
- // Staggered flashing with random timing for each cell
85
- LaunchedEffect(selectedImages) {
86
- // Nothing to flash without images — and selectedImages.random() below would throw on an
87
- // empty list (e.g. the loading screen shown for an empty draft).
88
- if (selectedImages.isEmpty()) return@LaunchedEffect
89
- // Launch individual coroutines for each cell with random timing
90
- for (i in 0 until gridSize) {
91
- launch {
92
- while (true) {
93
- if (flashingStates[i]) {
94
- // Hide image
95
- flashingStates = flashingStates.toMutableList().apply {
96
- this[i] = false
97
- }
98
-
99
- val hiddenTime = (300..500).random().toLong()
100
- delay(hiddenTime)
101
- } else {
102
- // Select a new random image for this specific cell
103
- val newImages = currentImages.toMutableList()
104
- newImages[i] = selectedImages.random()
105
- currentImages = newImages
106
-
107
- // Show image
108
- flashingStates = flashingStates.toMutableList().apply {
109
- this[i] = true
110
- }
111
-
112
- val visibleTime = (300..400).random().toLong()
113
- delay(visibleTime)
114
- }
115
- }
116
- }
117
- }
118
- }
119
-
120
- Box(
121
- modifier = modifier
122
- .fillMaxSize()
123
- .background(MaterialTheme.colorScheme.background)
124
- // Swallow all input so taps/gestures don't reach the still-composed (alpha 0) editor
125
- // underneath while it is loading.
126
- .pointerInput(Unit) {
127
- awaitPointerEventScope {
128
- while (true) {
129
- awaitPointerEvent().changes.forEach { it.consume() }
130
- }
131
- }
132
- },
133
- contentAlignment = Alignment.Center,
134
- ) {
135
- Column(
136
- horizontalAlignment = Alignment.CenterHorizontally,
137
- verticalArrangement = Arrangement.spacedBy(32.dp),
138
- ) {
139
- // 4x4 Image Grid
140
- Box {
141
- LazyVerticalGrid(
142
- columns = GridCells.Fixed(4),
143
- horizontalArrangement = Arrangement.spacedBy(12.dp),
144
- verticalArrangement = Arrangement.spacedBy(12.dp),
145
- modifier = Modifier
146
- .alpha(gridAlpha)
147
- .aspectRatio(1f)
148
- .fillMaxSize()
149
- .padding(32.dp),
150
- userScrollEnabled = false,
151
- ) {
152
- items(gridSize) { index ->
153
- val isFlashing = flashingStates[index]
154
- val imageToShow = currentImages[index]
155
-
156
- val alpha by animateFloatAsState(
157
- targetValue = if (isFlashing) 1f else 0f,
158
- animationSpec = tween(300),
159
- label = "flash_alpha",
160
- )
161
-
162
- val scale by animateFloatAsState(
163
- targetValue = if (isFlashing) .9f else 1f,
164
- animationSpec = spring(),
165
- label = "flash_scale",
166
- )
167
-
168
- Box(
169
- modifier = Modifier
170
- .scale(scale)
171
- .aspectRatio(1f)
172
- .background(
173
- MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
174
- shape = RoundedCornerShape(8.dp),
175
- ),
176
- ) {
177
- if (imageToShow != null) {
178
- AsyncImage(
179
- model = imageToShow.url,
180
- contentDescription = null,
181
- modifier = Modifier
182
- .fillMaxSize()
183
- .clip(RoundedCornerShape(8.dp))
184
- .alpha(alpha),
185
- contentScale = ContentScale.Crop,
186
- )
187
- }
188
- }
189
- }
190
- }
191
-
192
- // Loading Text
193
- val isDark = isSystemInDarkTheme()
194
- val backgroundColor = MaterialTheme.colorScheme.background
195
- Column(
196
- modifier = Modifier
197
- .aspectRatio(1f)
198
- .fillMaxSize()
199
- .drawBehind {
200
- scale(
201
- scaleY = .8f,
202
- scaleX = 1f,
203
- ) {
204
- drawRect(
205
- brush = Brush.radialGradient(
206
- colors = listOf(
207
- backgroundColor.copy(alpha = if (isDark) .85f else 1f),
208
- Color.Transparent,
209
- ),
210
- ),
211
- )
212
- }
213
- }
214
- .padding(32.dp)
215
- .align(Alignment.Center),
216
- horizontalAlignment = Alignment.CenterHorizontally,
217
- verticalArrangement = Arrangement.spacedBy(
218
- space = 8.dp,
219
- alignment = Alignment.CenterVertically,
220
- ),
221
- ) {
222
- Text(
223
- text = "Hold tight",
224
- style = MaterialTheme.typography.headlineMedium,
225
- fontWeight = FontWeight.Bold,
226
- color = MaterialTheme.colorScheme.onBackground,
227
- modifier = Modifier.alpha(titleAlpha),
228
- )
229
- Text(
230
- text = "We're magically creating\nmemories for you ✨",
231
- style = MaterialTheme.typography.bodyLarge,
232
- textAlign = TextAlign.Center,
233
- color = MaterialTheme.colorScheme.onBackground,
234
- modifier = Modifier
235
- .alpha(descriptionAlpha)
236
- .padding(horizontal = 32.dp),
237
- )
238
- }
239
- }
240
- }
241
- }
242
- }
243
-
244
- @PreviewLightDark
245
- @Preview(name = "Loading Screen", showBackground = true)
246
- @Composable
247
- private fun LoadingScreen_Preview() {
248
- EditorTheme {
249
- LoadingScreen(
250
- selectedImages = List(6) { index ->
251
- ImageItem(
252
- id = index + 1,
253
- url = "",
254
- )
255
- },
256
- )
257
- }
258
- }