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

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 (79) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/src/main/assets/ly.img.memories.style/content.json +50 -0
  3. package/android/src/main/assets/ly.img.memories.style/thumbnails/bubblegum.png +0 -0
  4. package/android/src/main/assets/ly.img.memories.style/thumbnails/hologram.png +0 -0
  5. package/android/src/main/assets/ly.img.memories.style/thumbnails/noir.png +0 -0
  6. package/android/src/main/assets/ly.img.memories.style/videos/bubblegum.mp4 +0 -0
  7. package/android/src/main/assets/ly.img.memories.style/videos/hologram.mp4 +0 -0
  8. package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesApp.kt +77 -0
  9. package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesConfiguration.kt +38 -0
  10. package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesEditor.kt +79 -0
  11. package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesViewModel.kt +469 -0
  12. package/android/src/main/kotlin/ly/img/editor/configuration/memories/callback/OnCreate.kt +69 -0
  13. package/android/src/main/kotlin/ly/img/editor/configuration/memories/callback/OnExport.kt +46 -0
  14. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/BottomPanel.kt +151 -0
  15. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/Dock.kt +145 -0
  16. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/ExportOverlay.kt +229 -0
  17. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/NavigationBar.kt +80 -0
  18. package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/Overlay.kt +138 -0
  19. package/android/src/main/kotlin/ly/img/editor/configuration/memories/extension/EditorScopeExt.kt +111 -0
  20. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/AddPhotoAlternate.kt +79 -0
  21. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/ArrowForward.kt +45 -0
  22. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Audiotrack.kt +43 -0
  23. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Check.kt +39 -0
  24. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/CheckCircle.kt +44 -0
  25. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Close.kt +43 -0
  26. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Delete.kt +54 -0
  27. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/DeleteOutline.kt +53 -0
  28. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Fullscreen.kt +74 -0
  29. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Image.kt +51 -0
  30. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Info.kt +48 -0
  31. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Pause.kt +46 -0
  32. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/PlayArrow.kt +38 -0
  33. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/RepeatOff.kt +80 -0
  34. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/RepeatOn.kt +59 -0
  35. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/SkipPrevious.kt +46 -0
  36. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Sort.kt +55 -0
  37. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Style.kt +59 -0
  38. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Title.kt +43 -0
  39. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/TouchTriple.kt +134 -0
  40. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/VolumeDown.kt +48 -0
  41. package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/VolumeUp.kt +60 -0
  42. package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/ExportStatus.kt +20 -0
  43. package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/ImageItem.kt +8 -0
  44. package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/TimelineImage.kt +18 -0
  45. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Events.kt +59 -0
  46. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Playback.kt +29 -0
  47. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/SceneSetup.kt +79 -0
  48. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Timeline.kt +27 -0
  49. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Title.kt +162 -0
  50. package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/TrackEditor.kt +740 -0
  51. package/android/src/main/kotlin/ly/img/editor/configuration/memories/screen/ImageSelectionScreen.kt +387 -0
  52. package/android/src/main/kotlin/ly/img/editor/configuration/memories/screen/LoadingScreen.kt +258 -0
  53. package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/FilterStylesSheet.kt +167 -0
  54. package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/FilterStylesSheetType.kt +21 -0
  55. package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/VolumeSheet.kt +104 -0
  56. package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/StyleApplier.kt +220 -0
  57. package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/StyleAssetSource.kt +43 -0
  58. package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/VideoStyle.kt +118 -0
  59. package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Animations.kt +58 -0
  60. package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Constants.kt +20 -0
  61. package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/ImageDate.kt +81 -0
  62. package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Utils.kt +27 -0
  63. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/CenteredAddImageButton.kt +82 -0
  64. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/DraggableImageItem.kt +145 -0
  65. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/FullscreenVideoControls.kt +136 -0
  66. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageGrid.kt +122 -0
  67. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageGridOverlay.kt +149 -0
  68. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageTimelineStrip.kt +323 -0
  69. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageToolbar.kt +401 -0
  70. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ReorderableGridState.kt +183 -0
  71. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/VideoControlPanel.kt +180 -0
  72. package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/VideoThumbnail.kt +110 -0
  73. package/android/src/main/kotlin/ly/img/editor/configuration/video/callback/OnCreate.kt +0 -7
  74. package/android/src/main/kotlin/ly/img/editor/configuration/video/component/InspectorBar.kt +2 -0
  75. package/ios/StarterKits/Video/callbacks/OnCreate+Video.swift +1 -1
  76. package/ios/StarterKits/Video/components/Dock+Video.swift +1 -0
  77. package/ios/StarterKits/Video/components/InspectorBar+Video.swift +4 -0
  78. package/package.json +1 -1
  79. package/src/NativeIMGLYEditor.ts +1 -1
@@ -0,0 +1,151 @@
1
+ package ly.img.editor.configuration.memories.component
2
+
3
+ import androidx.compose.animation.core.tween
4
+ import androidx.compose.animation.expandVertically
5
+ import androidx.compose.animation.fadeIn
6
+ import androidx.compose.animation.fadeOut
7
+ import androidx.compose.animation.shrinkVertically
8
+ import androidx.compose.foundation.background
9
+ import androidx.compose.foundation.layout.Box
10
+ import androidx.compose.foundation.layout.Column
11
+ import androidx.compose.foundation.layout.defaultMinSize
12
+ import androidx.compose.foundation.layout.fillMaxWidth
13
+ import androidx.compose.material3.MaterialTheme
14
+ import androidx.compose.runtime.Composable
15
+ import androidx.compose.runtime.LaunchedEffect
16
+ import androidx.compose.runtime.collectAsState
17
+ import androidx.compose.runtime.getValue
18
+ import androidx.compose.runtime.mutableStateOf
19
+ import androidx.compose.runtime.remember
20
+ import androidx.compose.runtime.setValue
21
+ import androidx.compose.ui.Modifier
22
+ import androidx.compose.ui.unit.dp
23
+ import kotlinx.coroutines.delay
24
+ import ly.img.editor.configuration.memories.MemoriesViewModel
25
+ import ly.img.editor.configuration.memories.scene.findTracks
26
+ import ly.img.editor.configuration.memories.scene.readTimelineImages
27
+ import ly.img.editor.configuration.memories.scene.readTitleClearTime
28
+ import ly.img.editor.configuration.memories.sheet.createVolumeSheetType
29
+ import ly.img.editor.configuration.memories.widget.ImageTimelineStrip
30
+ import ly.img.editor.configuration.memories.widget.VideoControlPanel
31
+ import ly.img.editor.configuration.memories.widget.buildStripItems
32
+ import ly.img.editor.core.EditorScope
33
+ import ly.img.editor.core.component.EditorComponent
34
+ import ly.img.editor.core.component.EditorTrigger
35
+ import ly.img.editor.core.component.NavigationBar
36
+ import ly.img.editor.core.component.remember
37
+ import ly.img.editor.core.event.EditorEvent
38
+ import ly.img.editor.core.state.EditorViewMode
39
+ import ly.img.editor.core.theme.surface1
40
+
41
+ fun bottomPanelConfiguration(viewModel: MemoriesViewModel): @Composable (EditorScope.() -> EditorComponent<*>) =
42
+ @Composable {
43
+ val isPlaying by viewModel.isPlaying.collectAsState()
44
+ val shouldHideUIForPreview by viewModel.shouldHideUIForPreview.collectAsState()
45
+ val isLooping by viewModel.isLooping.collectAsState()
46
+ val volume by viewModel.volume.collectAsState()
47
+
48
+ EditorComponent.remember {
49
+ scope = {
50
+ val historyTrigger by EditorTrigger.remember {
51
+ editorContext.engine.editor.onHistoryUpdated()
52
+ }
53
+ remember(this, historyTrigger) {
54
+ NavigationBar.Scope(parentScope = this)
55
+ }
56
+ }
57
+ enterTransition = {
58
+ expandVertically(animationSpec = tween(200)) + fadeIn()
59
+ }
60
+ exitTransition = {
61
+ shrinkVertically(animationSpec = tween(200)) + fadeOut()
62
+ }
63
+ visible = {
64
+ val state by editorContext.state.collectAsState()
65
+ (state.viewMode !is EditorViewMode.Preview) && !shouldHideUIForPreview
66
+ }
67
+ decoration = {
68
+ Box(
69
+ modifier = Modifier
70
+ .defaultMinSize(minHeight = 1.dp)
71
+ .background(
72
+ MaterialTheme.colorScheme.surface1.copy(alpha = 0.95f),
73
+ ),
74
+ ) {
75
+ val page = editorContext.engine.scene.getCurrentPage() ?: return@Box
76
+ val engine = editorContext.engine
77
+
78
+ // Current playback time, polled to drive the timeline strip cursor.
79
+ var currentTime by remember { mutableStateOf(0.0) }
80
+
81
+ LaunchedEffect(Unit) {
82
+ while (true) {
83
+ currentTime = engine.block.getPlaybackTime(page)
84
+ delay(100) // Update every 100ms for smoother display
85
+ }
86
+ }
87
+
88
+ Column(modifier = Modifier.fillMaxWidth()) {
89
+ // The strip reads its items straight from the engine tracks and rebuilds
90
+ // itself on every history change (apply, undo, redo) — see ImageTimelineStrip.
91
+ ImageTimelineStrip(
92
+ engine = engine,
93
+ buildItems = {
94
+ val tracks = findTracks(engine, page)
95
+ if (tracks != null) {
96
+ val images = readTimelineImages(engine, tracks)
97
+ val titleClearTime = readTitleClearTime(engine, page, tracks)
98
+ buildStripItems(images, titleClearTime)
99
+ } else {
100
+ emptyList()
101
+ }
102
+ },
103
+ currentTime = currentTime,
104
+ onSeekToTime = { time ->
105
+ engine.block.setPlaybackTime(page, time)
106
+ },
107
+ )
108
+
109
+ VideoControlPanel(
110
+ isPlaying = isPlaying,
111
+ isLooping = isLooping,
112
+ onPlayPauseClick = {
113
+ viewModel.setPlaying(!isPlaying)
114
+ engine.block.setPlaying(
115
+ block = page,
116
+ enabled = !isPlaying,
117
+ )
118
+ },
119
+ onRestartClick = {
120
+ engine.block.setPlaybackTime(page, 0.0)
121
+ engine.block.setPlaying(page, false)
122
+ viewModel.setPlaying(false)
123
+ },
124
+ onLoopClick = {
125
+ viewModel.toggleLoop()
126
+ },
127
+ onVolumeClick = {
128
+ editorContext.eventHandler.send(
129
+ EditorEvent.Sheet.Open(
130
+ createVolumeSheetType(
131
+ initialVolume = volume,
132
+ onVolumeChange = { newVolume ->
133
+ viewModel.setVolume(newVolume)
134
+ },
135
+ ),
136
+ ),
137
+ )
138
+ },
139
+ onFullscreenClick = {
140
+ // Enter preview/fullscreen and start playing (the bottom panel is only
141
+ // visible outside preview, so this always enters).
142
+ viewModel.togglePreviewMode()
143
+ engine.block.setPlaying(page, true)
144
+ viewModel.setPlaying(true)
145
+ },
146
+ )
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,145 @@
1
+ package ly.img.editor.configuration.memories.component
2
+
3
+ import androidx.compose.animation.core.tween
4
+ import androidx.compose.animation.expandVertically
5
+ import androidx.compose.animation.fadeIn
6
+ import androidx.compose.animation.fadeOut
7
+ import androidx.compose.animation.shrinkVertically
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.padding
12
+ import androidx.compose.foundation.shape.CircleShape
13
+ import androidx.compose.material3.MaterialTheme
14
+ import androidx.compose.runtime.Composable
15
+ import androidx.compose.runtime.collectAsState
16
+ import androidx.compose.runtime.getValue
17
+ import androidx.compose.ui.Modifier
18
+ import androidx.compose.ui.draw.shadow
19
+ import androidx.compose.ui.graphics.Color
20
+ import androidx.compose.ui.unit.dp
21
+ import ly.img.editor.configuration.memories.MemoriesViewModel
22
+ import ly.img.editor.configuration.memories.iconPack.Audiotrack
23
+ import ly.img.editor.configuration.memories.iconPack.Image
24
+ import ly.img.editor.configuration.memories.iconPack.Style
25
+ import ly.img.editor.configuration.memories.sheet.createFilterStylesSheetType
26
+ import ly.img.editor.core.EditorScope
27
+ import ly.img.editor.core.component.Dock
28
+ import ly.img.editor.core.component.EditorComponent
29
+ import ly.img.editor.core.component.EditorComponentId
30
+ import ly.img.editor.core.component.remember
31
+ import ly.img.editor.core.event.EditorEvent
32
+ import ly.img.editor.core.iconpack.IconPack
33
+ import ly.img.editor.core.library.LibraryCategory
34
+ import ly.img.editor.core.sheet.SheetType
35
+ import ly.img.editor.core.state.EditorViewMode
36
+ import ly.img.editor.core.theme.surface1
37
+ import ly.img.editor.core.theme.surface3
38
+
39
+ fun dockConfiguration(viewModel: MemoriesViewModel): @Composable (EditorScope.() -> EditorComponent<*>) = dock@{
40
+ val showImageGrid by viewModel.showImageGrid.collectAsState()
41
+ val shouldHideUIForPreview by viewModel.shouldHideUIForPreview.collectAsState()
42
+
43
+ Dock.remember {
44
+ enterTransition = {
45
+ expandVertically(animationSpec = tween(200)) + fadeIn()
46
+ }
47
+ exitTransition = {
48
+ shrinkVertically(animationSpec = tween(200)) + fadeOut()
49
+ }
50
+ decoration = { content ->
51
+ val state by editorContext.state.collectAsState()
52
+ Box(
53
+ modifier = Modifier
54
+ .background(
55
+ MaterialTheme.colorScheme.surface1.copy(alpha = 0.95f),
56
+ )
57
+ .padding(8.dp)
58
+ .padding(horizontal = 32.dp)
59
+ .shadow(
60
+ elevation = 3.dp,
61
+ shape = CircleShape,
62
+ spotColor = Color.Black.copy(alpha = .6f),
63
+ )
64
+ .background(
65
+ MaterialTheme.colorScheme.surface3.copy(alpha = 0.95f),
66
+ shape = CircleShape,
67
+ ),
68
+ ) {
69
+ content()
70
+ }
71
+ }
72
+ horizontalArrangement = { Arrangement.SpaceEvenly }
73
+ visible = {
74
+ val state by editorContext.state.collectAsState()
75
+ (state.viewMode !is EditorViewMode.Preview) && !shouldHideUIForPreview
76
+ }
77
+ listBuilder = {
78
+ Dock.ListBuilder.remember {
79
+ // Media (image + video) library button
80
+ add {
81
+ Dock.Button.remember {
82
+ id = { EditorComponentId("ly.img.images") }
83
+ vectorIcon = { IconPack.Image }
84
+ textString = { "Media" }
85
+ onClick = {
86
+ if (showImageGrid) {
87
+ // Already open → treat as cancel
88
+ viewModel.cancelOverlay()
89
+ } else {
90
+ // Seed the editable grid from the tracks (source of truth)
91
+ viewModel.openImageGridFromTracks(editorContext)
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ // Styles button
98
+ add {
99
+ Dock.Button.remember {
100
+ id = { EditorComponentId("ly.img.styles") }
101
+ vectorIcon = { IconPack.Style }
102
+ textString = { "Styles" }
103
+ onClick = {
104
+ editorContext.eventHandler.send(
105
+ EditorEvent.Sheet.Open(
106
+ createFilterStylesSheetType(
107
+ thumbnails = viewModel.styleThumbnails.value,
108
+ // Preselect the currently-applied style on reopen.
109
+ initialSelectedId = viewModel.activeStyleId,
110
+ ) { style ->
111
+ viewModel.applyStyle(editorContext, style.id)
112
+ },
113
+ ),
114
+ )
115
+ }
116
+ }
117
+ }
118
+
119
+ // Audio library button
120
+ add {
121
+ Dock.Button.remember {
122
+ id = { EditorComponentId("ly.img.audio") }
123
+ vectorIcon = { IconPack.Audiotrack }
124
+ textString = { "Audio" }
125
+ onClick = {
126
+ // Open the SDK's built-in audio library
127
+ editorContext.eventHandler.send(
128
+ EditorEvent.Sheet.Open(
129
+ SheetType.LibraryAdd(
130
+ libraryCategory = LibraryCategory.Audio,
131
+ ),
132
+ ),
133
+ )
134
+ editorContext.engine.scene.getCurrentPage()?.let { page ->
135
+ editorContext.engine.block.setPlaying(page, false)
136
+ // Keep the ViewModel in sync, else the next play/pause needs a double tap.
137
+ viewModel.setPlaying(false)
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,229 @@
1
+ package ly.img.editor.configuration.memories.component
2
+
3
+ import androidx.compose.animation.core.LinearEasing
4
+ import androidx.compose.animation.core.animateFloat
5
+ import androidx.compose.animation.core.animateFloatAsState
6
+ import androidx.compose.animation.core.infiniteRepeatable
7
+ import androidx.compose.animation.core.rememberInfiniteTransition
8
+ import androidx.compose.animation.core.tween
9
+ import androidx.compose.foundation.Canvas
10
+ import androidx.compose.foundation.background
11
+ import androidx.compose.foundation.gestures.detectTapGestures
12
+ import androidx.compose.foundation.layout.Arrangement
13
+ import androidx.compose.foundation.layout.Box
14
+ import androidx.compose.foundation.layout.Column
15
+ import androidx.compose.foundation.layout.Spacer
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.layout.size
22
+ import androidx.compose.foundation.shape.RoundedCornerShape
23
+ import androidx.compose.material3.ButtonDefaults
24
+ import androidx.compose.material3.Icon
25
+ import androidx.compose.material3.MaterialTheme
26
+ import androidx.compose.material3.ProgressIndicatorDefaults
27
+ import androidx.compose.material3.Surface
28
+ import androidx.compose.material3.Text
29
+ import androidx.compose.material3.TextButton
30
+ import androidx.compose.runtime.Composable
31
+ import androidx.compose.runtime.getValue
32
+ import androidx.compose.ui.Alignment
33
+ import androidx.compose.ui.Modifier
34
+ import androidx.compose.ui.geometry.Offset
35
+ import androidx.compose.ui.geometry.Size
36
+ import androidx.compose.ui.graphics.Brush
37
+ import androidx.compose.ui.graphics.Color
38
+ import androidx.compose.ui.graphics.StrokeCap
39
+ import androidx.compose.ui.graphics.drawscope.DrawScope
40
+ import androidx.compose.ui.graphics.drawscope.Stroke
41
+ import androidx.compose.ui.graphics.drawscope.rotate
42
+ import androidx.compose.ui.input.pointer.pointerInput
43
+ import androidx.compose.ui.text.style.TextAlign
44
+ import androidx.compose.ui.unit.Dp
45
+ import androidx.compose.ui.unit.dp
46
+ import ly.img.editor.configuration.memories.MemoriesConfiguration
47
+ import ly.img.editor.configuration.memories.iconPack.CheckCircle
48
+ import ly.img.editor.configuration.memories.iconPack.Info
49
+ import ly.img.editor.configuration.memories.model.ExportStatus
50
+ import ly.img.editor.core.event.EditorEvent
51
+ import ly.img.editor.core.iconpack.IconPack
52
+
53
+ /** A bottom sheet over the editor showing export progress, then a share button (or an error). */
54
+ @Composable
55
+ fun MemoriesConfiguration.ExportOverlay() {
56
+ val status = exportStatus ?: return
57
+ Box(
58
+ modifier = Modifier
59
+ .fillMaxSize()
60
+ .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f))
61
+ .pointerInput(Unit) { detectTapGestures { } },
62
+ ) {
63
+ Surface(
64
+ modifier = Modifier.fillMaxWidth().align(Alignment.BottomStart),
65
+ shape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp),
66
+ shadowElevation = 16.dp,
67
+ ) {
68
+ Box(Modifier.fillMaxWidth().navigationBarsPadding().padding(16.dp)) {
69
+ when (status) {
70
+ is ExportStatus.Loading -> ExportLoading(status)
71
+ is ExportStatus.Success -> ExportContent(
72
+ center = { ExportIcon(IconPack.CheckCircle, MaterialTheme.colorScheme.primary) },
73
+ title = "Ready to share",
74
+ text = "Your memory has been exported.",
75
+ button = "Share",
76
+ onClick = {
77
+ exportStatus = null
78
+ shareFile(file = status.file, mimeType = status.mimeType)
79
+ },
80
+ )
81
+ is ExportStatus.Error -> ExportContent(
82
+ center = { ExportIcon(IconPack.Info, MaterialTheme.colorScheme.error) },
83
+ title = "Export failed",
84
+ text = "Something went wrong while exporting.",
85
+ button = "Dismiss",
86
+ onClick = { exportStatus = null },
87
+ )
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ @Composable
95
+ private fun MemoriesConfiguration.ExportLoading(status: ExportStatus.Loading) {
96
+ ExportContent(
97
+ center = {
98
+ Box(contentAlignment = Alignment.Center) {
99
+ ExportProgressIndicator(progress = status.progress)
100
+ Text(
101
+ text = "${(status.progress * 100).toInt()}%",
102
+ style = MaterialTheme.typography.labelLarge,
103
+ color = MaterialTheme.colorScheme.onSurface,
104
+ )
105
+ }
106
+ },
107
+ title = "Exporting",
108
+ text = "Creating your video memory.",
109
+ button = "Cancel",
110
+ buttonColor = MaterialTheme.colorScheme.error,
111
+ onClick = { editorContext.eventHandler.send(EditorEvent.Export.Cancel()) },
112
+ )
113
+ }
114
+
115
+ @Composable
116
+ private fun ExportIcon(
117
+ icon: androidx.compose.ui.graphics.vector.ImageVector,
118
+ tint: Color,
119
+ ) {
120
+ Icon(imageVector = icon, contentDescription = null, modifier = Modifier.size(144.dp), tint = tint)
121
+ }
122
+
123
+ @Composable
124
+ private fun ExportContent(
125
+ center: @Composable () -> Unit,
126
+ title: String,
127
+ text: String,
128
+ button: String,
129
+ buttonColor: Color = MaterialTheme.colorScheme.primary,
130
+ onClick: () -> Unit,
131
+ ) {
132
+ Column(
133
+ modifier = Modifier.fillMaxWidth(),
134
+ horizontalAlignment = Alignment.CenterHorizontally,
135
+ verticalArrangement = Arrangement.Center,
136
+ ) {
137
+ Spacer(Modifier.height(24.dp))
138
+ center()
139
+ Spacer(Modifier.height(24.dp))
140
+ Text(
141
+ title,
142
+ style = MaterialTheme.typography.headlineSmall,
143
+ color = MaterialTheme.colorScheme.onSurface,
144
+ textAlign = TextAlign.Center,
145
+ )
146
+ Spacer(Modifier.height(4.dp))
147
+ Text(
148
+ text,
149
+ style = MaterialTheme.typography.bodyMedium,
150
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
151
+ textAlign = TextAlign.Center,
152
+ )
153
+ Spacer(Modifier.height(24.dp))
154
+ TextButton(onClick = onClick, colors = ButtonDefaults.textButtonColors(contentColor = buttonColor)) {
155
+ Text(button)
156
+ }
157
+ }
158
+ }
159
+
160
+ /** A ring that fills with [progress] (0..1), with a softly rotating gradient backdrop. */
161
+ @Composable
162
+ private fun ExportProgressIndicator(
163
+ progress: Float,
164
+ fillColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
165
+ gradientColor: Color = MaterialTheme.colorScheme.onSurface,
166
+ strokeWidth: Dp = 8.dp,
167
+ size: Dp = 144.dp,
168
+ ) {
169
+ val animatedProgress by animateFloatAsState(progress, ProgressIndicatorDefaults.ProgressAnimationSpec, label = "progress")
170
+ val rotation by rememberInfiniteTransition(label = "ring").animateFloat(
171
+ initialValue = 0f,
172
+ targetValue = 360f,
173
+ animationSpec = infiniteRepeatable(tween(1500, easing = LinearEasing)),
174
+ label = "rotation",
175
+ )
176
+ val gradient = Brush.sweepGradient(
177
+ 0.02f to Color.Transparent,
178
+ 0.5f to gradientColor.copy(alpha = 0.24f),
179
+ 1f to Color.Transparent,
180
+ )
181
+
182
+ Canvas(Modifier.size(size - strokeWidth * 2)) {
183
+ val stroke = Stroke(width = strokeWidth.toPx(), cap = StrokeCap.Round)
184
+ rotate(-180f + rotation) { arc(gradient = gradient, startAngle = 0f, sweep = 180f, stroke = stroke) }
185
+ arc(color = fillColor, startAngle = 270f, sweep = animatedProgress * 360f, stroke = stroke)
186
+ }
187
+ }
188
+
189
+ private fun DrawScope.arc(
190
+ color: Color,
191
+ startAngle: Float,
192
+ sweep: Float,
193
+ stroke: Stroke,
194
+ ) {
195
+ val inset = stroke.width / 2
196
+ drawArc(
197
+ color,
198
+ startAngle,
199
+ sweep,
200
+ useCenter = false,
201
+ topLeft = Offset(inset, inset),
202
+ size = Size(
203
+ size.width - 2 * inset,
204
+ size.height - 2 * inset,
205
+ ),
206
+ style = stroke,
207
+ )
208
+ }
209
+
210
+ private fun DrawScope.arc(
211
+ gradient: Brush,
212
+ startAngle: Float,
213
+ sweep: Float,
214
+ stroke: Stroke,
215
+ ) {
216
+ val inset = stroke.width / 2
217
+ drawArc(
218
+ gradient,
219
+ startAngle,
220
+ sweep,
221
+ useCenter = false,
222
+ topLeft = Offset(inset, inset),
223
+ size = Size(
224
+ size.width - 2 * inset,
225
+ size.height - 2 * inset,
226
+ ),
227
+ style = stroke,
228
+ )
229
+ }
@@ -0,0 +1,80 @@
1
+ package ly.img.editor.configuration.memories.component
2
+
3
+ import androidx.compose.animation.core.tween
4
+ import androidx.compose.animation.fadeIn
5
+ import androidx.compose.animation.fadeOut
6
+ import androidx.compose.animation.slideInVertically
7
+ import androidx.compose.animation.slideOutVertically
8
+ import androidx.compose.foundation.background
9
+ import androidx.compose.foundation.layout.Box
10
+ import androidx.compose.material3.MaterialTheme
11
+ import androidx.compose.runtime.Composable
12
+ import androidx.compose.runtime.collectAsState
13
+ import androidx.compose.runtime.getValue
14
+ import androidx.compose.runtime.remember
15
+ import androidx.compose.ui.Alignment
16
+ import androidx.compose.ui.Modifier
17
+ import ly.img.editor.configuration.memories.MemoriesViewModel
18
+ import ly.img.editor.core.EditorScope
19
+ import ly.img.editor.core.component.EditorComponent
20
+ import ly.img.editor.core.component.EditorTrigger
21
+ import ly.img.editor.core.component.NavigationBar
22
+ import ly.img.editor.core.component.remember
23
+ import ly.img.editor.core.component.rememberCloseEditor
24
+ import ly.img.editor.core.component.rememberExport
25
+ import ly.img.editor.core.component.rememberRedo
26
+ import ly.img.editor.core.component.rememberUndo
27
+ import ly.img.editor.core.theme.surface3
28
+
29
+ fun navigationBarConfiguration(viewModel: MemoriesViewModel): @Composable (EditorScope.() -> EditorComponent<*>) = {
30
+ val isPreviewMode by viewModel.isPreviewMode.collectAsState()
31
+ val shouldHideUIForPreview by viewModel.shouldHideUIForPreview.collectAsState()
32
+
33
+ NavigationBar.remember {
34
+ scope = {
35
+ val historyTrigger by EditorTrigger.remember {
36
+ editorContext.engine.editor.onHistoryUpdated()
37
+ }
38
+ remember(this, historyTrigger) {
39
+ NavigationBar.Scope(parentScope = this)
40
+ }
41
+ }
42
+ visible = { !shouldHideUIForPreview }
43
+ enterTransition = {
44
+ slideInVertically(
45
+ animationSpec = tween(100),
46
+ initialOffsetY = { -it },
47
+ ) + fadeIn(tween(100))
48
+ }
49
+ exitTransition = {
50
+ slideOutVertically(
51
+ animationSpec = tween(100),
52
+ targetOffsetY = { -it },
53
+ ) + fadeOut(tween(100))
54
+ }
55
+ decoration = { content ->
56
+ Box(
57
+ modifier = Modifier
58
+ .background(
59
+ color = MaterialTheme.colorScheme.surface3.copy(alpha = 0.95f),
60
+ ),
61
+ ) {
62
+ content()
63
+ }
64
+ }
65
+ listBuilder = {
66
+ NavigationBar.ListBuilder.remember {
67
+ this.aligned(Alignment.Start) {
68
+ add { NavigationBar.Button.rememberCloseEditor {} }
69
+ }
70
+ this.aligned(Alignment.CenterHorizontally) {
71
+ add { NavigationBar.Button.rememberUndo {} }
72
+ add { NavigationBar.Button.rememberRedo {} }
73
+ }
74
+ this.aligned(Alignment.End) {
75
+ add { NavigationBar.Button.rememberExport() }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }