@imgly/editor-react-native 1.80.0 → 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.
- package/android/build.gradle +1 -1
- package/android/src/main/assets/ly.img.memories.style/content.json +50 -0
- package/android/src/main/assets/ly.img.memories.style/thumbnails/bubblegum.png +0 -0
- package/android/src/main/assets/ly.img.memories.style/thumbnails/hologram.png +0 -0
- package/android/src/main/assets/ly.img.memories.style/thumbnails/noir.png +0 -0
- package/android/src/main/assets/ly.img.memories.style/videos/bubblegum.mp4 +0 -0
- package/android/src/main/assets/ly.img.memories.style/videos/hologram.mp4 +0 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesApp.kt +77 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesConfiguration.kt +38 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesEditor.kt +79 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesViewModel.kt +469 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/callback/OnCreate.kt +69 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/callback/OnExport.kt +46 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/BottomPanel.kt +151 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/Dock.kt +145 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/ExportOverlay.kt +229 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/NavigationBar.kt +80 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/Overlay.kt +138 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/extension/EditorScopeExt.kt +111 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/AddPhotoAlternate.kt +79 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/ArrowForward.kt +45 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Audiotrack.kt +43 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Check.kt +39 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/CheckCircle.kt +44 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Close.kt +43 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Delete.kt +54 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/DeleteOutline.kt +53 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Fullscreen.kt +74 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Image.kt +51 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Info.kt +48 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Pause.kt +46 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/PlayArrow.kt +38 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/RepeatOff.kt +80 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/RepeatOn.kt +59 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/SkipPrevious.kt +46 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Sort.kt +55 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Style.kt +59 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Title.kt +43 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/TouchTriple.kt +134 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/VolumeDown.kt +48 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/VolumeUp.kt +60 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/ExportStatus.kt +20 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/ImageItem.kt +8 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/TimelineImage.kt +18 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Events.kt +59 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Playback.kt +29 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/SceneSetup.kt +79 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Timeline.kt +27 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Title.kt +162 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/TrackEditor.kt +740 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/screen/ImageSelectionScreen.kt +387 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/screen/LoadingScreen.kt +258 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/FilterStylesSheet.kt +167 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/FilterStylesSheetType.kt +21 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/VolumeSheet.kt +104 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/StyleApplier.kt +220 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/StyleAssetSource.kt +43 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/VideoStyle.kt +118 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Animations.kt +58 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Constants.kt +20 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/ImageDate.kt +81 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Utils.kt +27 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/CenteredAddImageButton.kt +82 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/DraggableImageItem.kt +145 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/FullscreenVideoControls.kt +136 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageGrid.kt +122 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageGridOverlay.kt +149 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageTimelineStrip.kt +323 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageToolbar.kt +401 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ReorderableGridState.kt +183 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/VideoControlPanel.kt +180 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/VideoThumbnail.kt +110 -0
- package/android/src/main/kotlin/ly/img/editor/configuration/video/callback/OnCreate.kt +0 -7
- package/android/src/main/kotlin/ly/img/editor/configuration/video/component/InspectorBar.kt +2 -0
- package/ios/StarterKits/Video/callbacks/OnCreate+Video.swift +1 -1
- package/ios/StarterKits/Video/components/Dock+Video.swift +1 -0
- package/ios/StarterKits/Video/components/InspectorBar+Video.swift +4 -0
- package/package.json +1 -1
- 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
|
+
}
|
package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/ExportOverlay.kt
ADDED
|
@@ -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
|
+
}
|
package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/NavigationBar.kt
ADDED
|
@@ -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
|
+
}
|