@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,134 @@
1
+ package ly.img.editor.configuration.memories.iconPack
2
+
3
+ import androidx.compose.ui.graphics.Color
4
+ import androidx.compose.ui.graphics.PathFillType
5
+ import androidx.compose.ui.graphics.SolidColor
6
+ import androidx.compose.ui.graphics.StrokeCap
7
+ import androidx.compose.ui.graphics.StrokeJoin
8
+ import androidx.compose.ui.graphics.vector.ImageVector
9
+ import androidx.compose.ui.graphics.vector.path
10
+ import androidx.compose.ui.unit.dp
11
+ import ly.img.editor.core.iconpack.IconPack
12
+
13
+ // Material Symbols Rounded "touch_triple" (filled) — used for the multi-select toggle.
14
+ val IconPack.TouchTriple: ImageVector
15
+ get() {
16
+ if (touchTriple != null) return touchTriple!!
17
+ touchTriple = ImageVector.Builder(
18
+ name = "touch_triple",
19
+ defaultWidth = 24.dp,
20
+ defaultHeight = 24.dp,
21
+ viewportWidth = 24f,
22
+ viewportHeight = 24f,
23
+ ).apply {
24
+ path(
25
+ fill = SolidColor(Color.Black),
26
+ fillAlpha = 1f,
27
+ stroke = null,
28
+ strokeAlpha = 1f,
29
+ strokeLineWidth = 1f,
30
+ strokeLineCap = StrokeCap.Butt,
31
+ strokeLineJoin = StrokeJoin.Bevel,
32
+ strokeLineMiter = 1f,
33
+ pathFillType = PathFillType.NonZero,
34
+ ) {
35
+ moveTo(19.95f, 9f)
36
+ quadTo(19.53f, 9f, 19.25f, 8.71f)
37
+ quadTo(18.98f, 8.42f, 18.98f, 8f)
38
+ quadToRelative(0f, -0.1f, 0.13f, -0.5f)
39
+ quadTo(19.3f, 7.15f, 19.4f, 6.77f)
40
+ reflectiveQuadTo(19.5f, 6f)
41
+ quadToRelative(0f, -0.5f, -0.14f, -0.99f)
42
+ reflectiveQuadTo(18.88f, 4.15f)
43
+ quadTo(18.73f, 3.97f, 18.63f, 3.76f)
44
+ quadTo(18.53f, 3.55f, 18.53f, 3.32f)
45
+ quadToRelative(0f, -0.38f, 0.26f, -0.65f)
46
+ reflectiveQuadTo(19.43f, 2.4f)
47
+ quadToRelative(0.32f, 0f, 0.59f, 0.16f)
48
+ quadToRelative(0.26f, 0.16f, 0.46f, 0.41f)
49
+ quadToRelative(0.55f, 0.63f, 0.79f, 1.41f)
50
+ reflectiveQuadTo(21.5f, 6f)
51
+ quadToRelative(0f, 0.65f, -0.16f, 1.29f)
52
+ quadTo(21.18f, 7.93f, 20.83f, 8.5f)
53
+ quadTo(20.7f, 8.73f, 20.46f, 8.86f)
54
+ reflectiveQuadTo(19.95f, 9f)
55
+ close()
56
+ moveToRelative(-4f, 0f)
57
+ quadTo(15.53f, 9f, 15.25f, 8.71f)
58
+ reflectiveQuadTo(14.98f, 8f)
59
+ quadToRelative(0f, -0.1f, 0.13f, -0.5f)
60
+ quadTo(15.3f, 7.15f, 15.4f, 6.77f)
61
+ reflectiveQuadTo(15.5f, 6f)
62
+ quadToRelative(0f, -0.5f, -0.14f, -0.99f)
63
+ reflectiveQuadTo(14.88f, 4.15f)
64
+ quadTo(14.73f, 3.97f, 14.63f, 3.76f)
65
+ quadTo(14.53f, 3.55f, 14.53f, 3.32f)
66
+ quadToRelative(0f, -0.38f, 0.26f, -0.65f)
67
+ reflectiveQuadTo(15.43f, 2.4f)
68
+ quadToRelative(0.32f, 0f, 0.59f, 0.16f)
69
+ quadToRelative(0.26f, 0.16f, 0.46f, 0.41f)
70
+ quadToRelative(0.55f, 0.63f, 0.79f, 1.41f)
71
+ reflectiveQuadTo(17.5f, 6f)
72
+ quadToRelative(0f, 0.65f, -0.16f, 1.29f)
73
+ quadTo(17.18f, 7.93f, 16.83f, 8.5f)
74
+ quadTo(16.7f, 8.73f, 16.46f, 8.86f)
75
+ reflectiveQuadTo(15.95f, 9f)
76
+ close()
77
+ moveTo(10.48f, 22f)
78
+ quadTo(9.78f, 22f, 9.16f, 21.7f)
79
+ reflectiveQuadTo(8.13f, 20.85f)
80
+ lineTo(3.1f, 14.48f)
81
+ quadTo(2.9f, 14.25f, 2.93f, 13.94f)
82
+ reflectiveQuadTo(3.15f, 13.43f)
83
+ quadTo(3.65f, 12.9f, 4.35f, 12.8f)
84
+ reflectiveQuadToRelative(1.3f, 0.28f)
85
+ lineTo(7.5f, 14.2f)
86
+ verticalLineTo(6f)
87
+ quadTo(7.5f, 5.57f, 7.79f, 5.29f)
88
+ reflectiveQuadTo(8.5f, 5f)
89
+ quadTo(8.93f, 5f, 9.23f, 5.29f)
90
+ quadTo(9.53f, 5.57f, 9.53f, 6f)
91
+ verticalLineToRelative(5f)
92
+ horizontalLineTo(17f)
93
+ quadToRelative(1.25f, 0f, 2.13f, 0.88f)
94
+ reflectiveQuadTo(20f, 14f)
95
+ verticalLineToRelative(4f)
96
+ quadToRelative(0f, 1.65f, -1.18f, 2.82f)
97
+ reflectiveQuadTo(16f, 22f)
98
+ horizontalLineTo(10.48f)
99
+ close()
100
+ moveTo(11.98f, 9f)
101
+ quadTo(11.55f, 9f, 11.26f, 8.71f)
102
+ reflectiveQuadTo(10.98f, 8f)
103
+ quadToRelative(0f, -0.05f, 0.13f, -0.5f)
104
+ quadTo(11.3f, 7.15f, 11.4f, 6.79f)
105
+ quadTo(11.5f, 6.43f, 11.5f, 6f)
106
+ quadToRelative(0f, -1.25f, -0.88f, -2.13f)
107
+ reflectiveQuadTo(8.5f, 3f)
108
+ reflectiveQuadTo(6.38f, 3.88f)
109
+ reflectiveQuadTo(5.5f, 6f)
110
+ quadToRelative(0f, 0.43f, 0.1f, 0.79f)
111
+ reflectiveQuadTo(5.9f, 7.5f)
112
+ quadTo(5.98f, 7.63f, 6f, 7.75f)
113
+ reflectiveQuadTo(6.03f, 8f)
114
+ quadToRelative(0f, 0.42f, -0.28f, 0.71f)
115
+ reflectiveQuadTo(5.05f, 9f)
116
+ quadTo(4.78f, 9f, 4.54f, 8.85f)
117
+ reflectiveQuadTo(4.18f, 8.48f)
118
+ quadTo(3.85f, 7.93f, 3.68f, 7.3f)
119
+ reflectiveQuadTo(3.5f, 6f)
120
+ quadTo(3.5f, 3.92f, 4.96f, 2.46f)
121
+ reflectiveQuadTo(8.5f, 1f)
122
+ reflectiveQuadToRelative(3.54f, 1.46f)
123
+ reflectiveQuadTo(13.5f, 6f)
124
+ quadToRelative(0f, 0.68f, -0.17f, 1.3f)
125
+ reflectiveQuadToRelative(-0.5f, 1.17f)
126
+ quadTo(12.7f, 8.7f, 12.48f, 8.85f)
127
+ reflectiveQuadTo(11.98f, 9f)
128
+ close()
129
+ }
130
+ }.build()
131
+ return touchTriple!!
132
+ }
133
+
134
+ private var touchTriple: ImageVector? = null
@@ -0,0 +1,48 @@
1
+ package ly.img.editor.configuration.memories.iconPack
2
+
3
+ import androidx.compose.ui.graphics.Color
4
+ import androidx.compose.ui.graphics.SolidColor
5
+ import androidx.compose.ui.graphics.vector.ImageVector
6
+ import androidx.compose.ui.graphics.vector.path
7
+ import androidx.compose.ui.unit.dp
8
+ import ly.img.editor.core.iconpack.IconPack
9
+
10
+ val IconPack.VolumeDown: ImageVector
11
+ get() {
12
+ if (_VolumeDown != null) {
13
+ return _VolumeDown!!
14
+ }
15
+ _VolumeDown = ImageVector.Builder(
16
+ name = "VolumeDown",
17
+ defaultWidth = 24.dp,
18
+ defaultHeight = 24.dp,
19
+ viewportWidth = 24f,
20
+ viewportHeight = 24f,
21
+ autoMirror = true,
22
+ ).apply {
23
+ path(fill = SolidColor(Color.Black)) {
24
+ moveTo(18.5f, 12f)
25
+ arcTo(4.5f, 4.5f, 0f, isMoreThanHalf = false, isPositiveArc = false, 16f, 7.97f)
26
+ verticalLineToRelative(8.05f)
27
+ curveToRelative(1.48f, -0.73f, 2.5f, -2.25f, 2.5f, -4.02f)
28
+ close()
29
+ moveTo(5f, 10f)
30
+ verticalLineToRelative(4f)
31
+ curveToRelative(0f, 0.55f, 0.45f, 1f, 1f, 1f)
32
+ horizontalLineToRelative(3f)
33
+ lineToRelative(3.29f, 3.29f)
34
+ curveToRelative(0.63f, 0.63f, 1.71f, 0.18f, 1.71f, -0.71f)
35
+ verticalLineTo(6.41f)
36
+ curveToRelative(0f, -0.89f, -1.08f, -1.34f, -1.71f, -0.71f)
37
+ lineTo(9f, 9f)
38
+ horizontalLineTo(6f)
39
+ curveToRelative(-0.55f, 0f, -1f, 0.45f, -1f, 1f)
40
+ close()
41
+ }
42
+ }.build()
43
+
44
+ return _VolumeDown!!
45
+ }
46
+
47
+ @Suppress("ObjectPropertyName")
48
+ private var _VolumeDown: ImageVector? = null
@@ -0,0 +1,60 @@
1
+ package ly.img.editor.configuration.memories.iconPack
2
+
3
+ import androidx.compose.ui.graphics.Color
4
+ import androidx.compose.ui.graphics.SolidColor
5
+ import androidx.compose.ui.graphics.vector.ImageVector
6
+ import androidx.compose.ui.graphics.vector.path
7
+ import androidx.compose.ui.unit.dp
8
+ import ly.img.editor.core.iconpack.IconPack
9
+
10
+ val IconPack.VolumeUp: ImageVector
11
+ get() {
12
+ if (_VolumeUp != null) {
13
+ return _VolumeUp!!
14
+ }
15
+ _VolumeUp = ImageVector.Builder(
16
+ name = "VolumeUp",
17
+ defaultWidth = 24.dp,
18
+ defaultHeight = 24.dp,
19
+ viewportWidth = 24f,
20
+ viewportHeight = 24f,
21
+ autoMirror = true,
22
+ ).apply {
23
+ path(fill = SolidColor(Color.Black)) {
24
+ moveTo(3f, 10f)
25
+ verticalLineToRelative(4f)
26
+ curveToRelative(0f, 0.55f, 0.45f, 1f, 1f, 1f)
27
+ horizontalLineToRelative(3f)
28
+ lineToRelative(3.29f, 3.29f)
29
+ curveToRelative(0.63f, 0.63f, 1.71f, 0.18f, 1.71f, -0.71f)
30
+ lineTo(12f, 6.41f)
31
+ curveToRelative(0f, -0.89f, -1.08f, -1.34f, -1.71f, -0.71f)
32
+ lineTo(7f, 9f)
33
+ lineTo(4f, 9f)
34
+ curveToRelative(-0.55f, 0f, -1f, 0.45f, -1f, 1f)
35
+ close()
36
+ moveTo(16.5f, 12f)
37
+ arcTo(4.5f, 4.5f, 0f, isMoreThanHalf = false, isPositiveArc = false, 14f, 7.97f)
38
+ verticalLineToRelative(8.05f)
39
+ curveToRelative(1.48f, -0.73f, 2.5f, -2.25f, 2.5f, -4.02f)
40
+ close()
41
+ moveTo(14f, 4.45f)
42
+ verticalLineToRelative(0.2f)
43
+ curveToRelative(0f, 0.38f, 0.25f, 0.71f, 0.6f, 0.85f)
44
+ curveTo(17.18f, 6.53f, 19f, 9.06f, 19f, 12f)
45
+ reflectiveCurveToRelative(-1.82f, 5.47f, -4.4f, 6.5f)
46
+ curveToRelative(-0.36f, 0.14f, -0.6f, 0.47f, -0.6f, 0.85f)
47
+ verticalLineToRelative(0.2f)
48
+ curveToRelative(0f, 0.63f, 0.63f, 1.07f, 1.21f, 0.85f)
49
+ curveTo(18.6f, 19.11f, 21f, 15.84f, 21f, 12f)
50
+ reflectiveCurveToRelative(-2.4f, -7.11f, -5.79f, -8.4f)
51
+ curveToRelative(-0.58f, -0.23f, -1.21f, 0.22f, -1.21f, 0.85f)
52
+ close()
53
+ }
54
+ }.build()
55
+
56
+ return _VolumeUp!!
57
+ }
58
+
59
+ @Suppress("ObjectPropertyName")
60
+ private var _VolumeUp: ImageVector? = null
@@ -0,0 +1,20 @@
1
+ package ly.img.editor.configuration.memories.model
2
+
3
+ import ly.img.engine.MimeType
4
+ import java.io.File
5
+
6
+ /** The state of the MP4 export, surfaced as a progress overlay. */
7
+ sealed interface ExportStatus {
8
+ data class Loading(
9
+ val progress: Float,
10
+ ) : ExportStatus
11
+
12
+ data class Success(
13
+ val file: File,
14
+ val mimeType: MimeType = MimeType.MP4,
15
+ ) : ExportStatus
16
+
17
+ data class Error(
18
+ val exception: Exception,
19
+ ) : ExportStatus
20
+ }
@@ -0,0 +1,8 @@
1
+ package ly.img.editor.configuration.memories.model
2
+
3
+ data class ImageItem(
4
+ val id: Int,
5
+ val url: String,
6
+ val creationDate: Long? = null, // Timestamp in milliseconds since epoch, null for stock images
7
+ val isVideo: Boolean = false, // true when [url] points to a video; placed as a trimmed video clip
8
+ )
@@ -0,0 +1,18 @@
1
+ package ly.img.editor.configuration.memories.model
2
+
3
+ /**
4
+ * An image as it sits on the video timeline, used by the bottom-panel thumbnail strip.
5
+ *
6
+ * @param url the image fill URI
7
+ * @param startTime when the image block starts on the timeline (seconds)
8
+ * @param endTime when the image block ends (seconds)
9
+ * @param fullyVisibleTime the moment the image is fully visible — right after its enter
10
+ * (Ken Burns) animation completes — which is where tapping the thumbnail seeks to.
11
+ */
12
+ data class TimelineImage(
13
+ val url: String,
14
+ val startTime: Double,
15
+ val endTime: Double,
16
+ val fullyVisibleTime: Double,
17
+ val isVideo: Boolean = false,
18
+ )
@@ -0,0 +1,59 @@
1
+ package ly.img.editor.configuration.memories.scene
2
+
3
+ import android.util.Log
4
+ import kotlinx.coroutines.delay
5
+ import kotlinx.coroutines.flow.first
6
+ import kotlinx.coroutines.launch
7
+ import ly.img.editor.configuration.memories.MemoriesViewModel
8
+ import ly.img.editor.core.EditorContext
9
+ import ly.img.engine.DesignBlockEvent
10
+ import ly.img.engine.Engine
11
+
12
+ private const val TAG = "MemoriesEvents"
13
+
14
+ private const val AUDIO_TYPE = "//ly.img.ubq/audio"
15
+ private const val MIN_LOAD_MILLIS = 2000L
16
+
17
+ internal fun setupEventSubscriptions(
18
+ engine: Engine,
19
+ editorContext: EditorContext,
20
+ viewModel: MemoriesViewModel,
21
+ ) {
22
+ subscribeMinimumLoad(editorContext, viewModel)
23
+ subscribeSingleAudioTrack(engine, editorContext)
24
+ }
25
+
26
+ /** Hold the loading screen for a minimum time so it doesn't flash. */
27
+ private fun subscribeMinimumLoad(
28
+ editorContext: EditorContext,
29
+ viewModel: MemoriesViewModel,
30
+ ) {
31
+ editorContext.coroutineScope.launch {
32
+ val elapsed = System.currentTimeMillis() - viewModel.loadingStartTime.first()
33
+ if (elapsed < MIN_LOAD_MILLIS) delay(MIN_LOAD_MILLIS - elapsed)
34
+ viewModel.setEditorLoading(false)
35
+ }
36
+ }
37
+
38
+ /** Keep a single audio track: destroy older audio blocks when a new one is added. */
39
+ private fun subscribeSingleAudioTrack(
40
+ engine: Engine,
41
+ editorContext: EditorContext,
42
+ ) {
43
+ editorContext.coroutineScope.launch {
44
+ engine.event.subscribe(listOf()).collect { events ->
45
+ events.filter { it.type == DesignBlockEvent.Type.CREATED }
46
+ .filter { runCatching { engine.block.getType(it.block) == AUDIO_TYPE }.getOrDefault(false) }
47
+ .forEach { event ->
48
+ try {
49
+ val parent = engine.block.getParent(event.block) ?: return@forEach
50
+ engine.block.getChildren(parent)
51
+ .filter { it != event.block && engine.block.getType(it) == AUDIO_TYPE }
52
+ .forEach { engine.block.destroy(it) }
53
+ } catch (e: Exception) {
54
+ Log.w(TAG, "Could not enforce single audio track", e)
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,29 @@
1
+ package ly.img.editor.configuration.memories.scene
2
+
3
+ import ly.img.engine.Engine
4
+
5
+ /** Playback engine ops on the slideshow page. The ViewModel holds the matching UI state. */
6
+
7
+ /** Flip the page's loop setting and return the new value (null if there's no page yet). */
8
+ internal fun togglePageLoop(engine: Engine): Boolean? {
9
+ val page = engine.scene.getCurrentPage() ?: return null
10
+ val next = !engine.block.isLooping(page)
11
+ engine.block.setLooping(page, next)
12
+ return next
13
+ }
14
+
15
+ internal fun setPageLooping(
16
+ engine: Engine,
17
+ looping: Boolean,
18
+ ) {
19
+ engine.scene.getCurrentPage()?.let { engine.block.setLooping(it, looping) }
20
+ }
21
+
22
+ internal fun setPageVolume(
23
+ engine: Engine,
24
+ volume: Float,
25
+ ) {
26
+ engine.scene.getCurrentPage()?.let { engine.block.setVolume(it, volume) }
27
+ }
28
+
29
+ internal fun getPageVolume(engine: Engine): Float? = engine.scene.getCurrentPage()?.let { engine.block.getVolume(it) }
@@ -0,0 +1,79 @@
1
+ package ly.img.editor.configuration.memories.scene
2
+
3
+ import ly.img.editor.configuration.memories.util.PAGE_HEIGHT
4
+ import ly.img.editor.configuration.memories.util.PAGE_WIDTH
5
+ import ly.img.engine.Color
6
+ import ly.img.engine.DesignBlock
7
+ import ly.img.engine.DesignBlockType
8
+ import ly.img.engine.Engine
9
+ import ly.img.engine.FillType
10
+ import ly.img.engine.ShapeType
11
+
12
+ /** Build the slideshow scene in code (no serialized blob): a video scene with one sized page. */
13
+ internal fun createSlideshowScene(engine: Engine): DesignBlock {
14
+ val scene = engine.scene.createForVideo()
15
+ val page = engine.block.create(DesignBlockType.Page)
16
+ engine.block.appendChild(parent = scene, child = page)
17
+ engine.block.setWidth(page, PAGE_WIDTH)
18
+ engine.block.setHeight(page, PAGE_HEIGHT)
19
+
20
+ val fill = engine.block.createFill(FillType.Color)
21
+ engine.block.setColor(fill, "fill/color/value", Color.fromRGBA(0f, 0f, 0f, 1f))
22
+ engine.block.setFill(page, fill)
23
+ return page
24
+ }
25
+
26
+ /**
27
+ * The persistent, single-purpose tracks of the slideshow, bottom-to-top in render order. The per-
28
+ * slide media tracks are created later (in [createMainImageSequence]) and stack on top of these.
29
+ */
30
+ internal data class TrackReferences(
31
+ val textTrack: DesignBlock,
32
+ val backgroundTrack: DesignBlock,
33
+ val backgroundBlock: DesignBlock,
34
+ val matteTrack: DesignBlock,
35
+ val matteBlock: DesignBlock,
36
+ )
37
+
38
+ internal fun setupTracks(
39
+ engine: Engine,
40
+ page: DesignBlock,
41
+ ): TrackReferences {
42
+ // background = per-style backdrop (hidden by default); matte = black rectangle so a crossfade
43
+ // fades to black instead of revealing the backdrop. Appended first → they render behind the
44
+ // media. The text (title) track sits just above them; the media tracks are appended on top later.
45
+ val backgroundTrack = engine.block.create(DesignBlockType.Track)
46
+ val matteTrack = engine.block.create(DesignBlockType.Track)
47
+ val textTrack = engine.block.create(DesignBlockType.Track)
48
+
49
+ // Tags let the rest of the kit re-locate tracks by role instead of retaining stale block ids.
50
+ tagSlideshowTracks(engine, textTrack = textTrack, backgroundTrack = backgroundTrack, matteTrack = matteTrack)
51
+
52
+ engine.block.appendChild(parent = page, child = backgroundTrack)
53
+ engine.block.appendChild(parent = page, child = matteTrack)
54
+ engine.block.appendChild(parent = page, child = textTrack)
55
+
56
+ val backgroundBlock = fullPageBlock(engine, page)
57
+ engine.block.setVisible(backgroundBlock, false)
58
+ engine.block.appendChild(parent = backgroundTrack, child = backgroundBlock)
59
+
60
+ val matteBlock = fullPageBlock(engine, page)
61
+ val matteFill = engine.block.createFill(FillType.Color)
62
+ engine.block.setColor(matteFill, "fill/color/value", Color.fromRGBA(0f, 0f, 0f, 1f))
63
+ engine.block.setFill(matteBlock, matteFill)
64
+ engine.block.appendChild(parent = matteTrack, child = matteBlock)
65
+
66
+ return TrackReferences(textTrack, backgroundTrack, backgroundBlock, matteTrack, matteBlock)
67
+ }
68
+
69
+ private fun fullPageBlock(
70
+ engine: Engine,
71
+ page: DesignBlock,
72
+ ): DesignBlock {
73
+ val block = engine.block.create(DesignBlockType.Graphic)
74
+ engine.block.setScopeEnabled(block, "editor/select", false)
75
+ engine.block.setShape(block, engine.block.createShape(ShapeType.Rect))
76
+ engine.block.setWidth(block, engine.block.getWidth(page))
77
+ engine.block.setHeight(block, engine.block.getHeight(page))
78
+ return block
79
+ }
@@ -0,0 +1,27 @@
1
+ package ly.img.editor.configuration.memories.scene
2
+
3
+ import ly.img.editor.configuration.memories.model.ImageItem
4
+ import ly.img.editor.configuration.memories.util.AnimationPair
5
+ import ly.img.engine.Engine
6
+
7
+ /**
8
+ * Lay the photos/clips out, one per track. Each track is appended on top of the previous one so a
9
+ * later slide always renders above the earlier one; the clips (videos included) are created first so
10
+ * their final durations are known, then every track is positioned in time by [repositionTracks] —
11
+ * consecutive slides overlap for the crossfade, a short video takes up only its own length, and
12
+ * photos next to a video hard-cut on that side (handled per-clip in [createMediaBlock]). Returns the
13
+ * per-slot durations so the caller can size the page and backdrops to match.
14
+ */
15
+ internal suspend fun createMainImageSequence(
16
+ engine: Engine,
17
+ page: Int,
18
+ images: List<ImageItem>,
19
+ hasTitle: Boolean,
20
+ animationPairs: List<AnimationPair>,
21
+ ): List<Double> {
22
+ images.forEachIndexed { index, _ ->
23
+ val track = createMediaTrack(engine, page, startTime = 0.0)
24
+ engine.block.appendChild(parent = track, child = createMediaBlock(engine, page, index, images, animationPairs))
25
+ }
26
+ return repositionTracks(engine, page, images, hasTitle)
27
+ }
@@ -0,0 +1,162 @@
1
+ package ly.img.editor.configuration.memories.scene
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import ly.img.editor.configuration.memories.model.ImageItem
6
+ import ly.img.editor.configuration.memories.style.VideoStyles
7
+ import ly.img.editor.configuration.memories.util.BURST_IMAGE_COUNT
8
+ import ly.img.editor.configuration.memories.util.BURST_TITLE_FRACTION
9
+ import ly.img.editor.configuration.memories.util.TITLE_DURATION
10
+ import ly.img.editor.configuration.memories.util.TITLE_FONT_SIZE
11
+ import ly.img.editor.configuration.memories.widget.videoThumbnailFileUri
12
+ import ly.img.engine.AnimationType
13
+ import ly.img.engine.Color
14
+ import ly.img.engine.DesignBlockType
15
+ import ly.img.engine.Engine
16
+ import ly.img.engine.FillType
17
+ import ly.img.engine.FindAssetsQuery
18
+ import ly.img.engine.ShapeType
19
+ import kotlin.random.Random
20
+
21
+ private const val TAG = "MemoriesTitle"
22
+
23
+ /** Create the centered, blur-animated title card on [textTrack]. Returns whether a title was added. */
24
+ internal suspend fun createTitleBlock(
25
+ engine: Engine,
26
+ page: Int,
27
+ title: String,
28
+ textTrack: Int,
29
+ ): Boolean {
30
+ if (title.isEmpty()) return false
31
+
32
+ val textBlock = engine.block.create(DesignBlockType.Text)
33
+ engine.block.replaceText(textBlock, title)
34
+ engine.block.setDuration(textBlock, TITLE_DURATION)
35
+ engine.block.setTextColor(textBlock, Color.fromRGBA(1f, 1f, 1f, 1f))
36
+ engine.block.setTextFontSize(textBlock, fontSize = TITLE_FONT_SIZE, from = 0, to = title.length)
37
+ engine.block.setMetadata(textBlock, "blockType", "title")
38
+
39
+ applyTitleTypeface(engine, textBlock, VideoStyles.DEFAULT.typeface)
40
+
41
+ engine.block.setInAnimation(textBlock, blurAnimation(engine, 0.8))
42
+ engine.block.setOutAnimation(textBlock, blurAnimation(engine, 0.6))
43
+
44
+ engine.block.appendChild(parent = textTrack, child = textBlock)
45
+ engine.block.setEnum(textBlock, "text/horizontalAlignment", "Center")
46
+ engine.block.setEnum(textBlock, "text/verticalAlignment", "Center")
47
+ return true
48
+ }
49
+
50
+ private fun blurAnimation(
51
+ engine: Engine,
52
+ duration: Double,
53
+ ): Int {
54
+ val animation = engine.block.createAnimation(AnimationType.Blur)
55
+ engine.block.setDuration(animation, duration)
56
+ engine.block.setEnum(animation, "textAnimationWritingStyle", "Character")
57
+ return animation
58
+ }
59
+
60
+ private suspend fun applyTitleTypeface(
61
+ engine: Engine,
62
+ textBlock: Int,
63
+ typefaceName: String,
64
+ ) {
65
+ try {
66
+ val assets = engine.asset.findAssets(
67
+ sourceId = "ly.img.typeface",
68
+ query = FindAssetsQuery(query = typefaceName, page = 0, perPage = 100),
69
+ )
70
+ val typeface = assets.assets.firstOrNull()?.payload?.typeface ?: return
71
+ // Match the weight preference the default style applies (see StyleApplier.resolveFont), so
72
+ // the first style application doesn't visibly re-weight the title from a different initial pick.
73
+ val font = VideoStyles.DEFAULT.fontWeights
74
+ .firstNotNullOfOrNull { weight -> typeface.fonts.firstOrNull { it.subFamily == weight } }
75
+ ?: typeface.fonts.firstOrNull()
76
+ ?: return
77
+ engine.block.setFont(textBlock, font.uri, typeface)
78
+ } catch (e: Exception) {
79
+ Log.w(TAG, "Could not load title typeface '$typefaceName'", e)
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Flash a handful of random photos behind the title for a lively opening. Videos can't be shown
85
+ * through an image fill, so each picked video is flashed as its extracted thumbnail instead (skipped
86
+ * if a frame can't be decoded, or if [context] is unavailable).
87
+ */
88
+ internal suspend fun createBurstImages(
89
+ engine: Engine,
90
+ page: Int,
91
+ context: Context?,
92
+ images: List<ImageItem>,
93
+ hasTitle: Boolean,
94
+ ) {
95
+ if (images.isEmpty() || !hasTitle) return
96
+
97
+ // Resolve each picked item to a renderable image URI: photos use their url, videos a thumbnail.
98
+ val urls = pickBurstImages(images, BURST_IMAGE_COUNT).mapNotNull { item ->
99
+ when {
100
+ !item.isVideo -> item.url
101
+ context != null -> videoThumbnailFileUri(context, item.url)
102
+ else -> null
103
+ }
104
+ }
105
+ if (urls.isEmpty()) return
106
+
107
+ val burstTracks = List(urls.size) {
108
+ engine.block.create(DesignBlockType.Track).also { engine.block.appendChild(page, it) }
109
+ }
110
+
111
+ val burstDuration = TITLE_DURATION * BURST_TITLE_FRACTION
112
+ val flashDuration = burstDuration / 6.0
113
+ val pageWidth = engine.block.getWidth(page)
114
+ val pageHeight = engine.block.getHeight(page)
115
+ val imageSize = minOf(pageWidth, pageHeight) * 0.6f
116
+
117
+ urls.forEachIndexed { index, url ->
118
+ val block = engine.block.create(DesignBlockType.Graphic)
119
+ engine.block.setScopeEnabled(block, "editor/select", false)
120
+ engine.block.setWidth(block, imageSize)
121
+ engine.block.setHeight(block, imageSize)
122
+ engine.block.setPositionX(block, randomIn(-imageSize / 2f, pageWidth - imageSize / 2f))
123
+ engine.block.setPositionY(block, randomIn(-imageSize / 2f, pageHeight - imageSize / 2f))
124
+
125
+ val fill = engine.block.createFill(FillType.Image)
126
+ engine.block.setFill(block, fill)
127
+ engine.block.setString(fill, "fill/image/imageFileURI", url)
128
+ engine.block.setShape(block, engine.block.createShape(ShapeType.Rect))
129
+ engine.block.setDuration(block, flashDuration)
130
+ engine.block.setInAnimation(block, fadeAnimation(engine, 0.1))
131
+ engine.block.setOutAnimation(block, fadeAnimation(engine, 0.15))
132
+
133
+ val startDelay = index * (flashDuration * 0.25)
134
+ if (startDelay > 0) {
135
+ engine.block.appendChild(burstTracks[index], createSpacerBlock(engine, page, startDelay))
136
+ }
137
+ engine.block.appendChild(burstTracks[index], block)
138
+ val remaining = TITLE_DURATION - (startDelay + flashDuration)
139
+ if (remaining > 0) {
140
+ engine.block.appendChild(burstTracks[index], createSpacerBlock(engine, page, remaining))
141
+ }
142
+ }
143
+ }
144
+
145
+ private fun pickBurstImages(
146
+ images: List<ImageItem>,
147
+ count: Int,
148
+ ): List<ImageItem> {
149
+ val shuffled = images.shuffled(Random(System.currentTimeMillis()))
150
+ if (shuffled.size >= count) return shuffled.take(count)
151
+ return generateSequence { shuffled }.flatten().take(count).toList()
152
+ }
153
+
154
+ private fun fadeAnimation(
155
+ engine: Engine,
156
+ duration: Double,
157
+ ): Int = engine.block.createAnimation(AnimationType.Fade).also { engine.block.setDuration(it, duration) }
158
+
159
+ private fun randomIn(
160
+ min: Float,
161
+ max: Float,
162
+ ): Float = min + Random.nextFloat() * (max - min)