@imgly/editor-react-native 1.81.0-rc.0 → 1.81.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 +4 -1
- package/package.json +1 -1
- package/src/NativeIMGLYEditor.ts +1 -1
- package/android/src/main/assets/ly.img.memories.style/content.json +0 -50
- 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 +0 -77
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesConfiguration.kt +0 -38
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesEditor.kt +0 -79
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/MemoriesViewModel.kt +0 -469
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/callback/OnCreate.kt +0 -69
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/callback/OnExport.kt +0 -46
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/BottomPanel.kt +0 -151
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/Dock.kt +0 -145
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/ExportOverlay.kt +0 -229
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/NavigationBar.kt +0 -80
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/component/Overlay.kt +0 -138
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/extension/EditorScopeExt.kt +0 -111
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/AddPhotoAlternate.kt +0 -79
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/ArrowForward.kt +0 -45
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Audiotrack.kt +0 -43
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Check.kt +0 -39
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/CheckCircle.kt +0 -44
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Close.kt +0 -43
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Delete.kt +0 -54
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/DeleteOutline.kt +0 -53
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Fullscreen.kt +0 -74
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Image.kt +0 -51
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Info.kt +0 -48
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Pause.kt +0 -46
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/PlayArrow.kt +0 -38
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/RepeatOff.kt +0 -80
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/RepeatOn.kt +0 -59
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/SkipPrevious.kt +0 -46
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Sort.kt +0 -55
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Style.kt +0 -59
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/Title.kt +0 -43
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/TouchTriple.kt +0 -134
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/VolumeDown.kt +0 -48
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/iconPack/VolumeUp.kt +0 -60
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/ExportStatus.kt +0 -20
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/ImageItem.kt +0 -8
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/model/TimelineImage.kt +0 -18
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Events.kt +0 -59
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Playback.kt +0 -29
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/SceneSetup.kt +0 -79
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Timeline.kt +0 -27
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/Title.kt +0 -162
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/scene/TrackEditor.kt +0 -740
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/screen/ImageSelectionScreen.kt +0 -387
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/screen/LoadingScreen.kt +0 -258
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/FilterStylesSheet.kt +0 -167
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/FilterStylesSheetType.kt +0 -21
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/sheet/VolumeSheet.kt +0 -104
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/StyleApplier.kt +0 -220
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/StyleAssetSource.kt +0 -43
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/style/VideoStyle.kt +0 -118
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Animations.kt +0 -58
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Constants.kt +0 -20
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/ImageDate.kt +0 -81
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/util/Utils.kt +0 -27
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/CenteredAddImageButton.kt +0 -82
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/DraggableImageItem.kt +0 -145
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/FullscreenVideoControls.kt +0 -136
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageGrid.kt +0 -122
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageGridOverlay.kt +0 -149
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageTimelineStrip.kt +0 -323
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ImageToolbar.kt +0 -401
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ReorderableGridState.kt +0 -183
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/VideoControlPanel.kt +0 -180
- package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/VideoThumbnail.kt +0 -110
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
@file:OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)
|
|
2
|
-
|
|
3
|
-
package ly.img.editor.configuration.memories.widget
|
|
4
|
-
|
|
5
|
-
import androidx.compose.animation.AnimatedVisibility
|
|
6
|
-
import androidx.compose.animation.scaleIn
|
|
7
|
-
import androidx.compose.animation.scaleOut
|
|
8
|
-
import androidx.compose.foundation.background
|
|
9
|
-
import androidx.compose.foundation.clickable
|
|
10
|
-
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
11
|
-
import androidx.compose.foundation.layout.Arrangement
|
|
12
|
-
import androidx.compose.foundation.layout.Box
|
|
13
|
-
import androidx.compose.foundation.layout.Row
|
|
14
|
-
import androidx.compose.foundation.layout.fillMaxHeight
|
|
15
|
-
import androidx.compose.foundation.layout.fillMaxSize
|
|
16
|
-
import androidx.compose.foundation.layout.fillMaxWidth
|
|
17
|
-
import androidx.compose.foundation.layout.height
|
|
18
|
-
import androidx.compose.foundation.layout.padding
|
|
19
|
-
import androidx.compose.foundation.layout.size
|
|
20
|
-
import androidx.compose.foundation.shape.CircleShape
|
|
21
|
-
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
22
|
-
import androidx.compose.material.ripple.rememberRipple
|
|
23
|
-
import androidx.compose.material3.AlertDialog
|
|
24
|
-
import androidx.compose.material3.Badge
|
|
25
|
-
import androidx.compose.material3.BadgedBox
|
|
26
|
-
import androidx.compose.material3.Card
|
|
27
|
-
import androidx.compose.material3.CardDefaults
|
|
28
|
-
import androidx.compose.material3.Icon
|
|
29
|
-
import androidx.compose.material3.IconButton
|
|
30
|
-
import androidx.compose.material3.LocalContentColor
|
|
31
|
-
import androidx.compose.material3.MaterialTheme
|
|
32
|
-
import androidx.compose.material3.Text
|
|
33
|
-
import androidx.compose.material3.TextButton
|
|
34
|
-
import androidx.compose.material3.minimumInteractiveComponentSize
|
|
35
|
-
import androidx.compose.runtime.Composable
|
|
36
|
-
import androidx.compose.runtime.CompositionLocalProvider
|
|
37
|
-
import androidx.compose.runtime.getValue
|
|
38
|
-
import androidx.compose.runtime.mutableStateOf
|
|
39
|
-
import androidx.compose.runtime.remember
|
|
40
|
-
import androidx.compose.runtime.setValue
|
|
41
|
-
import androidx.compose.ui.Alignment
|
|
42
|
-
import androidx.compose.ui.Modifier
|
|
43
|
-
import androidx.compose.ui.graphics.Color
|
|
44
|
-
import androidx.compose.ui.graphics.graphicsLayer
|
|
45
|
-
import androidx.compose.ui.semantics.Role
|
|
46
|
-
import androidx.compose.ui.tooling.preview.Preview
|
|
47
|
-
import androidx.compose.ui.unit.dp
|
|
48
|
-
import ly.img.editor.configuration.memories.iconPack.AddPhotoAlternate
|
|
49
|
-
import ly.img.editor.configuration.memories.iconPack.ArrowForward
|
|
50
|
-
import ly.img.editor.configuration.memories.iconPack.Check
|
|
51
|
-
import ly.img.editor.configuration.memories.iconPack.Delete
|
|
52
|
-
import ly.img.editor.configuration.memories.iconPack.DeleteOutline
|
|
53
|
-
import ly.img.editor.configuration.memories.iconPack.Sort
|
|
54
|
-
import ly.img.editor.configuration.memories.iconPack.TouchTriple
|
|
55
|
-
import ly.img.editor.core.iconpack.IconPack
|
|
56
|
-
|
|
57
|
-
@Composable
|
|
58
|
-
fun ImageToolbar(
|
|
59
|
-
modifier: Modifier = Modifier,
|
|
60
|
-
onAddImage: () -> Unit = {},
|
|
61
|
-
onToggleSort: () -> Unit = {},
|
|
62
|
-
onDeleteAll: (() -> Unit)? = null,
|
|
63
|
-
onDeleteSelected: () -> Unit = {},
|
|
64
|
-
onProceed: () -> Unit = {},
|
|
65
|
-
isSortEnabled: Boolean = false,
|
|
66
|
-
isSortAscending: Boolean = true,
|
|
67
|
-
hasImages: Boolean = false,
|
|
68
|
-
showDeleteButton: Boolean = true,
|
|
69
|
-
selectedForDeletionCount: Int = 0,
|
|
70
|
-
totalCount: Int = 0,
|
|
71
|
-
confirmMode: Boolean = false,
|
|
72
|
-
multiSelectMode: Boolean = false,
|
|
73
|
-
onToggleMultiSelect: () -> Unit = {},
|
|
74
|
-
) {
|
|
75
|
-
var showDeleteDialog by remember { mutableStateOf(false) }
|
|
76
|
-
|
|
77
|
-
Row(
|
|
78
|
-
modifier = modifier
|
|
79
|
-
.fillMaxWidth(),
|
|
80
|
-
horizontalArrangement = Arrangement.spacedBy(
|
|
81
|
-
12.dp,
|
|
82
|
-
alignment = Alignment.CenterHorizontally,
|
|
83
|
-
),
|
|
84
|
-
verticalAlignment = Alignment.CenterVertically,
|
|
85
|
-
) {
|
|
86
|
-
// Left pill with multiple actions
|
|
87
|
-
Card(
|
|
88
|
-
modifier = Modifier
|
|
89
|
-
.weight(1f, false)
|
|
90
|
-
.height(64.dp),
|
|
91
|
-
shape = CircleShape,
|
|
92
|
-
elevation = CardDefaults.elevatedCardElevation(
|
|
93
|
-
defaultElevation = 3.dp,
|
|
94
|
-
),
|
|
95
|
-
colors = CardDefaults.cardColors(
|
|
96
|
-
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
|
97
|
-
),
|
|
98
|
-
) {
|
|
99
|
-
Row(
|
|
100
|
-
modifier = Modifier
|
|
101
|
-
.fillMaxHeight()
|
|
102
|
-
.padding(horizontal = 8.dp),
|
|
103
|
-
horizontalArrangement = Arrangement.Center,
|
|
104
|
-
verticalAlignment = Alignment.CenterVertically,
|
|
105
|
-
) {
|
|
106
|
-
// Add photos button — hidden while selecting images to delete. Its trailing
|
|
107
|
-
// gap lives inside the AnimatedVisibility so it collapses with the button.
|
|
108
|
-
AnimatedVisibility(visible = !multiSelectMode) {
|
|
109
|
-
IconButton(
|
|
110
|
-
onClick = onAddImage,
|
|
111
|
-
modifier = Modifier.padding(end = 4.dp),
|
|
112
|
-
) {
|
|
113
|
-
Icon(
|
|
114
|
-
imageVector = IconPack.AddPhotoAlternate,
|
|
115
|
-
contentDescription = "Add photos",
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Multi-select toggle
|
|
121
|
-
IconButton(
|
|
122
|
-
onClick = onToggleMultiSelect,
|
|
123
|
-
modifier = Modifier
|
|
124
|
-
.background(
|
|
125
|
-
color = if (multiSelectMode) {
|
|
126
|
-
MaterialTheme.colorScheme.primaryContainer
|
|
127
|
-
} else {
|
|
128
|
-
Color.Transparent
|
|
129
|
-
},
|
|
130
|
-
shape = CircleShape,
|
|
131
|
-
),
|
|
132
|
-
) {
|
|
133
|
-
Icon(
|
|
134
|
-
imageVector = IconPack.TouchTriple,
|
|
135
|
-
contentDescription = if (multiSelectMode) {
|
|
136
|
-
"Exit selection mode"
|
|
137
|
-
} else {
|
|
138
|
-
"Select media"
|
|
139
|
-
},
|
|
140
|
-
tint = if (multiSelectMode) {
|
|
141
|
-
MaterialTheme.colorScheme.onPrimaryContainer
|
|
142
|
-
} else {
|
|
143
|
-
LocalContentColor.current
|
|
144
|
-
},
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Sort button
|
|
149
|
-
IconButton(
|
|
150
|
-
onClick = onToggleSort,
|
|
151
|
-
modifier = Modifier
|
|
152
|
-
.padding(start = 4.dp)
|
|
153
|
-
.background(
|
|
154
|
-
color = if (isSortEnabled) {
|
|
155
|
-
MaterialTheme.colorScheme.primaryContainer
|
|
156
|
-
} else {
|
|
157
|
-
Color.Transparent
|
|
158
|
-
},
|
|
159
|
-
shape = CircleShape,
|
|
160
|
-
),
|
|
161
|
-
) {
|
|
162
|
-
Icon(
|
|
163
|
-
imageVector = IconPack.Sort,
|
|
164
|
-
contentDescription = "Sort by date",
|
|
165
|
-
modifier = Modifier
|
|
166
|
-
.graphicsLayer(
|
|
167
|
-
scaleY = if (isSortEnabled && !isSortAscending) -1f else 1f,
|
|
168
|
-
),
|
|
169
|
-
tint = if (isSortEnabled) {
|
|
170
|
-
MaterialTheme.colorScheme.onPrimaryContainer
|
|
171
|
-
} else {
|
|
172
|
-
LocalContentColor.current
|
|
173
|
-
},
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Delete button. Off: filled trash → delete all. On: outlined trash with a
|
|
178
|
-
// count badge (from 0) → delete the current selection.
|
|
179
|
-
if (showDeleteButton) {
|
|
180
|
-
val deleteEnabled =
|
|
181
|
-
if (multiSelectMode) selectedForDeletionCount > 0 else hasImages
|
|
182
|
-
// A plain IconButton clips its content to a circular state layer, which cuts off
|
|
183
|
-
// the BadgedBox count badge at the icon's corner. Use an unclipped, unbounded button.
|
|
184
|
-
UnboundedIconButton(
|
|
185
|
-
onClick = { showDeleteDialog = true },
|
|
186
|
-
enabled = deleteEnabled,
|
|
187
|
-
modifier = Modifier.padding(start = 4.dp),
|
|
188
|
-
) {
|
|
189
|
-
BadgedBox(
|
|
190
|
-
badge = {
|
|
191
|
-
SelectionCountBadge(
|
|
192
|
-
visible = multiSelectMode,
|
|
193
|
-
count = selectedForDeletionCount,
|
|
194
|
-
)
|
|
195
|
-
},
|
|
196
|
-
) {
|
|
197
|
-
Icon(
|
|
198
|
-
imageVector = if (multiSelectMode) {
|
|
199
|
-
IconPack.DeleteOutline
|
|
200
|
-
} else {
|
|
201
|
-
IconPack.Delete
|
|
202
|
-
},
|
|
203
|
-
contentDescription = if (multiSelectMode) {
|
|
204
|
-
"Delete selected media"
|
|
205
|
-
} else {
|
|
206
|
-
"Delete all media"
|
|
207
|
-
},
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Right pill - Proceed button
|
|
216
|
-
Card(
|
|
217
|
-
modifier = Modifier
|
|
218
|
-
.size(56.dp),
|
|
219
|
-
shape = RoundedCornerShape(14.dp),
|
|
220
|
-
elevation = CardDefaults.elevatedCardElevation(
|
|
221
|
-
defaultElevation = 3.dp,
|
|
222
|
-
),
|
|
223
|
-
colors = CardDefaults.cardColors(
|
|
224
|
-
containerColor = if (hasImages) {
|
|
225
|
-
MaterialTheme.colorScheme.primary
|
|
226
|
-
} else {
|
|
227
|
-
MaterialTheme.colorScheme.surfaceVariant
|
|
228
|
-
},
|
|
229
|
-
),
|
|
230
|
-
) {
|
|
231
|
-
IconButton(
|
|
232
|
-
onClick = onProceed,
|
|
233
|
-
enabled = hasImages,
|
|
234
|
-
modifier = Modifier.fillMaxSize(),
|
|
235
|
-
) {
|
|
236
|
-
Icon(
|
|
237
|
-
imageVector = if (confirmMode) {
|
|
238
|
-
IconPack.Check
|
|
239
|
-
} else {
|
|
240
|
-
IconPack.ArrowForward
|
|
241
|
-
},
|
|
242
|
-
contentDescription = if (confirmMode) "Apply changes" else "Proceed",
|
|
243
|
-
tint = if (hasImages) {
|
|
244
|
-
MaterialTheme.colorScheme.onPrimary
|
|
245
|
-
} else {
|
|
246
|
-
MaterialTheme.colorScheme.onSurfaceVariant
|
|
247
|
-
},
|
|
248
|
-
)
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (showDeleteDialog) {
|
|
254
|
-
val count = if (multiSelectMode) selectedForDeletionCount else totalCount
|
|
255
|
-
val noun = if (count == 1) "item" else "items"
|
|
256
|
-
DeleteConfirmationDialog(
|
|
257
|
-
title = if (multiSelectMode) "Delete selected media?" else "Delete all media?",
|
|
258
|
-
message = if (multiSelectMode) {
|
|
259
|
-
"Remove $count selected $noun? This can't be undone."
|
|
260
|
-
} else {
|
|
261
|
-
"Remove all $count $noun? This can't be undone."
|
|
262
|
-
},
|
|
263
|
-
onConfirm = {
|
|
264
|
-
showDeleteDialog = false
|
|
265
|
-
if (multiSelectMode) onDeleteSelected() else onDeleteAll?.invoke()
|
|
266
|
-
},
|
|
267
|
-
onDismiss = { showDeleteDialog = false },
|
|
268
|
-
)
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* An icon button that does not clip its content to a circle (so a [BadgedBox] badge can overflow
|
|
274
|
-
* past the icon's corner) and ripples without a bounded circular mask.
|
|
275
|
-
*/
|
|
276
|
-
@Composable
|
|
277
|
-
private fun UnboundedIconButton(
|
|
278
|
-
onClick: () -> Unit,
|
|
279
|
-
modifier: Modifier = Modifier,
|
|
280
|
-
enabled: Boolean = true,
|
|
281
|
-
content: @Composable () -> Unit,
|
|
282
|
-
) {
|
|
283
|
-
Box(
|
|
284
|
-
modifier = modifier
|
|
285
|
-
.minimumInteractiveComponentSize()
|
|
286
|
-
.size(48.dp)
|
|
287
|
-
.clickable(
|
|
288
|
-
interactionSource = remember { MutableInteractionSource() },
|
|
289
|
-
indication = rememberRipple(bounded = false, radius = 22.dp),
|
|
290
|
-
enabled = enabled,
|
|
291
|
-
role = Role.Button,
|
|
292
|
-
onClick = onClick,
|
|
293
|
-
),
|
|
294
|
-
contentAlignment = Alignment.Center,
|
|
295
|
-
) {
|
|
296
|
-
val baseColor = LocalContentColor.current
|
|
297
|
-
CompositionLocalProvider(
|
|
298
|
-
LocalContentColor provides if (enabled) baseColor else baseColor.copy(alpha = 0.38f),
|
|
299
|
-
content = content,
|
|
300
|
-
)
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
@Composable
|
|
305
|
-
private fun SelectionCountBadge(
|
|
306
|
-
visible: Boolean,
|
|
307
|
-
count: Int,
|
|
308
|
-
) {
|
|
309
|
-
AnimatedVisibility(
|
|
310
|
-
visible,
|
|
311
|
-
enter = scaleIn(initialScale = 0f),
|
|
312
|
-
exit = scaleOut(targetScale = 0f),
|
|
313
|
-
) {
|
|
314
|
-
Badge {
|
|
315
|
-
Text("$count")
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
@Composable
|
|
321
|
-
private fun DeleteConfirmationDialog(
|
|
322
|
-
title: String,
|
|
323
|
-
message: String,
|
|
324
|
-
onConfirm: () -> Unit,
|
|
325
|
-
onDismiss: () -> Unit,
|
|
326
|
-
) {
|
|
327
|
-
AlertDialog(
|
|
328
|
-
onDismissRequest = onDismiss,
|
|
329
|
-
icon = {
|
|
330
|
-
Icon(imageVector = IconPack.Delete, contentDescription = null)
|
|
331
|
-
},
|
|
332
|
-
title = { Text(title) },
|
|
333
|
-
text = { Text(message) },
|
|
334
|
-
confirmButton = {
|
|
335
|
-
TextButton(onClick = onConfirm) {
|
|
336
|
-
Text("Delete", color = MaterialTheme.colorScheme.error)
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
dismissButton = {
|
|
340
|
-
TextButton(onClick = onDismiss) {
|
|
341
|
-
Text("Cancel")
|
|
342
|
-
}
|
|
343
|
-
},
|
|
344
|
-
)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
@Preview(name = "Empty State", showBackground = true)
|
|
348
|
-
@Composable
|
|
349
|
-
fun ImageToolbar_EmptyState_Preview() {
|
|
350
|
-
MaterialTheme {
|
|
351
|
-
ImageToolbar(
|
|
352
|
-
modifier = Modifier.padding(16.dp),
|
|
353
|
-
hasImages = false,
|
|
354
|
-
showDeleteButton = true,
|
|
355
|
-
onDeleteAll = {},
|
|
356
|
-
)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
@Preview(name = "With Images - Clear all", showBackground = true)
|
|
361
|
-
@Composable
|
|
362
|
-
fun ImageToolbar_WithImages_Preview() {
|
|
363
|
-
MaterialTheme {
|
|
364
|
-
ImageToolbar(
|
|
365
|
-
modifier = Modifier.padding(16.dp),
|
|
366
|
-
hasImages = true,
|
|
367
|
-
totalCount = 8,
|
|
368
|
-
showDeleteButton = true,
|
|
369
|
-
onDeleteAll = {},
|
|
370
|
-
)
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
@Preview(name = "Multi-select mode (badge)", showBackground = true)
|
|
375
|
-
@Composable
|
|
376
|
-
fun ImageToolbar_SelectionMode_Preview() {
|
|
377
|
-
MaterialTheme {
|
|
378
|
-
ImageToolbar(
|
|
379
|
-
modifier = Modifier.padding(16.dp),
|
|
380
|
-
hasImages = true,
|
|
381
|
-
totalCount = 8,
|
|
382
|
-
selectedForDeletionCount = 3,
|
|
383
|
-
showDeleteButton = true,
|
|
384
|
-
onDeleteAll = {},
|
|
385
|
-
multiSelectMode = true,
|
|
386
|
-
)
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
@Preview(name = "Overlay Mode (No Delete)", showBackground = true)
|
|
391
|
-
@Composable
|
|
392
|
-
fun ImageToolbar_OverlayMode_Preview() {
|
|
393
|
-
MaterialTheme {
|
|
394
|
-
ImageToolbar(
|
|
395
|
-
modifier = Modifier.padding(16.dp),
|
|
396
|
-
hasImages = true,
|
|
397
|
-
isSortEnabled = true,
|
|
398
|
-
showDeleteButton = false,
|
|
399
|
-
)
|
|
400
|
-
}
|
|
401
|
-
}
|
package/android/src/main/kotlin/ly/img/editor/configuration/memories/widget/ReorderableGridState.kt
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
package ly.img.editor.configuration.memories.widget
|
|
2
|
-
|
|
3
|
-
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
|
|
4
|
-
import androidx.compose.foundation.lazy.grid.LazyGridState
|
|
5
|
-
import androidx.compose.runtime.Composable
|
|
6
|
-
import androidx.compose.runtime.State
|
|
7
|
-
import androidx.compose.runtime.getValue
|
|
8
|
-
import androidx.compose.runtime.mutableFloatStateOf
|
|
9
|
-
import androidx.compose.runtime.mutableStateOf
|
|
10
|
-
import androidx.compose.runtime.remember
|
|
11
|
-
import androidx.compose.runtime.rememberUpdatedState
|
|
12
|
-
import androidx.compose.runtime.setValue
|
|
13
|
-
import androidx.compose.ui.geometry.Offset
|
|
14
|
-
import androidx.compose.ui.graphics.graphicsLayer
|
|
15
|
-
import androidx.compose.ui.platform.LocalDensity
|
|
16
|
-
import androidx.compose.ui.unit.IntOffset
|
|
17
|
-
import androidx.compose.ui.unit.dp
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Holds the live drag state. Kept outside the composable so the gesture callbacks can mutate it
|
|
21
|
-
* without re-running composition until something observable actually changes.
|
|
22
|
-
*
|
|
23
|
-
* Consumed by [ImageGrid] via [rememberReorderableGridState]: it drives the drag-and-drop
|
|
24
|
-
* reordering of the media grid built on Compose Foundation / AndroidX APIs (no third-party
|
|
25
|
-
* reorder libraries).
|
|
26
|
-
*/
|
|
27
|
-
class ReorderableGridState internal constructor(
|
|
28
|
-
private val gridState: LazyGridState,
|
|
29
|
-
private val scrollThresholdPx: Float,
|
|
30
|
-
private val maxScrollPerFramePx: Float,
|
|
31
|
-
private val scrollActivationSlopPx: Float,
|
|
32
|
-
private val onMove: (Int, Int) -> Unit,
|
|
33
|
-
) {
|
|
34
|
-
// Current index of the dragged item. Changes as it swaps past neighbours.
|
|
35
|
-
var draggingItemIndex by mutableStateOf<Int?>(null)
|
|
36
|
-
private set
|
|
37
|
-
|
|
38
|
-
val isDragging: Boolean get() = draggingItemIndex != null
|
|
39
|
-
|
|
40
|
-
// Auto-scroll speed in px/frame, set during onDrag and consumed by the LaunchedEffect.
|
|
41
|
-
var autoScrollSpeed by mutableFloatStateOf(0f)
|
|
42
|
-
private set
|
|
43
|
-
|
|
44
|
-
// Layout offset of the item at pick-up (viewport coords) plus the finger's accumulated travel.
|
|
45
|
-
private var initialOffset by mutableStateOf(Offset.Zero)
|
|
46
|
-
private var draggedDelta by mutableStateOf(Offset.Zero)
|
|
47
|
-
|
|
48
|
-
private val draggingItemLayoutInfo: LazyGridItemInfo?
|
|
49
|
-
get() = gridState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex }
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* The translation to apply to the dragged cell's [graphicsLayer]. Drawing the cell at
|
|
53
|
-
* `currentLayoutOffset + draggingItemOffset` always lands it at `initialOffset + draggedDelta`
|
|
54
|
-
* — i.e. exactly under the finger — no matter where its layout slot currently sits. That is the
|
|
55
|
-
* re-anchor: when a swap changes the item's slot (or auto-scroll shifts the layout), this value
|
|
56
|
-
* compensates automatically instead of snapping back.
|
|
57
|
-
*/
|
|
58
|
-
val draggingItemOffset: Offset
|
|
59
|
-
get() = draggingItemLayoutInfo?.let { item ->
|
|
60
|
-
initialOffset + draggedDelta - item.offset.toOffset()
|
|
61
|
-
} ?: Offset.Zero
|
|
62
|
-
|
|
63
|
-
fun onDragStart(offset: Offset) {
|
|
64
|
-
// detectDragGesturesAfterLongPress reports the touch relative to the grid node's top-left
|
|
65
|
-
// (the viewport, which includes the content-padding region). visibleItemsInfo offsets live in
|
|
66
|
-
// the item coordinate space whose origin sits at viewportStartOffset (= -beforeContentPadding
|
|
67
|
-
// on the main axis). Shift the pointer onto that space before hit-testing, or a large top
|
|
68
|
-
// content padding makes us pick a cell below the one actually touched. Vertical grid → only
|
|
69
|
-
// the main (y) axis is affected.
|
|
70
|
-
val pointer = offset.copy(y = offset.y + gridState.layoutInfo.viewportStartOffset)
|
|
71
|
-
gridState.layoutInfo.visibleItemsInfo
|
|
72
|
-
.firstOrNull { item -> pointer.inBounds(item) }
|
|
73
|
-
?.also {
|
|
74
|
-
draggingItemIndex = it.index
|
|
75
|
-
initialOffset = it.offset.toOffset()
|
|
76
|
-
draggedDelta = Offset.Zero
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
fun onDrag(dragAmount: Offset) {
|
|
81
|
-
draggedDelta += dragAmount
|
|
82
|
-
val dragged = draggingItemLayoutInfo ?: return
|
|
83
|
-
val from = draggingItemIndex ?: return
|
|
84
|
-
|
|
85
|
-
// Visual top-left then centre of the dragged cell, in viewport coords.
|
|
86
|
-
val topLeft = dragged.offset.toOffset() + draggingItemOffset
|
|
87
|
-
val center = topLeft + Offset(dragged.size.width / 2f, dragged.size.height / 2f)
|
|
88
|
-
|
|
89
|
-
// Hit-test the centre against every other visible cell; swap into the first that contains it.
|
|
90
|
-
val target = gridState.layoutInfo.visibleItemsInfo.firstOrNull { item ->
|
|
91
|
-
item.index != from && center.inBounds(item)
|
|
92
|
-
}
|
|
93
|
-
if (target != null) {
|
|
94
|
-
onMove(from, target.index)
|
|
95
|
-
// Re-point to the new index; draggingItemOffset re-anchors so the cell stays put.
|
|
96
|
-
draggingItemIndex = target.index
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
autoScrollSpeed = computeAutoScroll(topLeft.y, dragged.size.height.toFloat())
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
fun onDragInterrupted() {
|
|
103
|
-
draggingItemIndex = null
|
|
104
|
-
initialOffset = Offset.Zero
|
|
105
|
-
draggedDelta = Offset.Zero
|
|
106
|
-
autoScrollSpeed = 0f
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Positive = scroll down, negative = scroll up, 0 = no auto-scroll.
|
|
111
|
-
*
|
|
112
|
-
* Being inside an edge zone is not enough: a cell picked up while already sitting in the top (or
|
|
113
|
-
* bottom) zone must not yank the list the instant the gesture moves. So we also require the user
|
|
114
|
-
* to have dragged *toward* that edge past [scrollActivationSlopPx]. [draggedDelta] is cumulative
|
|
115
|
-
* finger travel since pick-up (auto-scroll does not change it), so a horizontal-only drag keeps
|
|
116
|
-
* `draggedDelta.y ≈ 0` and never triggers a scroll.
|
|
117
|
-
*/
|
|
118
|
-
private fun computeAutoScroll(
|
|
119
|
-
top: Float,
|
|
120
|
-
height: Float,
|
|
121
|
-
): Float {
|
|
122
|
-
val bottom = top + height
|
|
123
|
-
val viewStart = gridState.layoutInfo.viewportStartOffset.toFloat()
|
|
124
|
-
val viewEnd = gridState.layoutInfo.viewportEndOffset.toFloat()
|
|
125
|
-
return when {
|
|
126
|
-
bottom > viewEnd - scrollThresholdPx && draggedDelta.y > scrollActivationSlopPx ->
|
|
127
|
-
(bottom - (viewEnd - scrollThresholdPx)).coerceAtMost(maxScrollPerFramePx)
|
|
128
|
-
top < viewStart + scrollThresholdPx && draggedDelta.y < -scrollActivationSlopPx ->
|
|
129
|
-
(top - (viewStart + scrollThresholdPx)).coerceAtLeast(-maxScrollPerFramePx)
|
|
130
|
-
else -> 0f
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private fun Offset.inBounds(item: LazyGridItemInfo): Boolean = x >= item.offset.x &&
|
|
135
|
-
x <= item.offset.x + item.size.width &&
|
|
136
|
-
y >= item.offset.y &&
|
|
137
|
-
y <= item.offset.y + item.size.height
|
|
138
|
-
|
|
139
|
-
private fun IntOffset.toOffset() = Offset(x.toFloat(), y.toFloat())
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
@Composable
|
|
143
|
-
fun rememberReorderableGridState(
|
|
144
|
-
gridState: LazyGridState,
|
|
145
|
-
onMove: (from: Int, to: Int) -> Unit,
|
|
146
|
-
): ReorderableGridState {
|
|
147
|
-
val density = LocalDensity.current
|
|
148
|
-
val scrollThresholdPx = with(density) { 72.dp.toPx() }
|
|
149
|
-
val maxScrollPerFramePx = with(density) { 16.dp.toPx() }
|
|
150
|
-
// How far the user must drag toward an edge before auto-scroll kicks in (prevents an instant
|
|
151
|
-
// jump when a cell is picked up already sitting in an edge zone).
|
|
152
|
-
val scrollActivationSlopPx = with(density) { 24.dp.toPx() }
|
|
153
|
-
// Always route to the latest onMove even though the state object itself is remembered.
|
|
154
|
-
val onMoveState: State<(Int, Int) -> Unit> = rememberUpdatedState(onMove)
|
|
155
|
-
return remember(gridState, scrollThresholdPx, maxScrollPerFramePx, scrollActivationSlopPx) {
|
|
156
|
-
ReorderableGridState(
|
|
157
|
-
gridState = gridState,
|
|
158
|
-
scrollThresholdPx = scrollThresholdPx,
|
|
159
|
-
maxScrollPerFramePx = maxScrollPerFramePx,
|
|
160
|
-
scrollActivationSlopPx = scrollActivationSlopPx,
|
|
161
|
-
onMove = { from, to -> onMoveState.value(from, to) },
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/*
|
|
167
|
-
* Edge cases this minimal version intentionally does not cover:
|
|
168
|
-
*
|
|
169
|
-
* - Auto-scroll alone does not re-run hit-testing. Swaps happen in onDrag, which only fires on
|
|
170
|
-
* finger movement. If you hold the finger still in an edge zone the grid scrolls but no new
|
|
171
|
-
* swaps occur until you nudge the finger. To fix, also call the swap logic from the scroll loop.
|
|
172
|
-
*
|
|
173
|
-
* - GridCells.Adaptive / variable-span items are untested. Hit-testing assumes uniform cells laid
|
|
174
|
-
* out left-to-right, top-to-bottom; spanned items can produce surprising targets.
|
|
175
|
-
*
|
|
176
|
-
* - No drop animation. When released, the cell jumps from its dragged position to its final slot.
|
|
177
|
-
* Animate draggingItemOffset toward zero on release for a settle effect.
|
|
178
|
-
*
|
|
179
|
-
* - No haptics on pick-up — add a LocalHapticFeedback.performHapticFeedback in onDragStart.
|
|
180
|
-
*
|
|
181
|
-
* - A null key falls back to position-based identity, which breaks animateItem() move animations
|
|
182
|
-
* and can mis-track the dragged item across data changes. Provide a stable key in real use.
|
|
183
|
-
*/
|