mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-15 01:33:25 +01:00
Add media duration to videos in v3 media select screen.
This commit is contained in:
committed by
Greyson Parrelli
parent
d40b4382e9
commit
9dc19c885a
+2
-3
@@ -14,9 +14,9 @@ import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.util.bytes
|
||||
import org.signal.mediasend.MediaConstraints
|
||||
import org.signal.mediasend.util.formatAsClock
|
||||
import org.thoughtcrime.securesms.video.TranscodingConfig
|
||||
import org.thoughtcrime.securesms.video.TranscodingQuality
|
||||
import java.util.Locale
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -39,10 +39,9 @@ internal fun VideoSizeHint(
|
||||
|
||||
// A trim drag emits a flood of recompositions, and formatting is the only work that has to follow each one.
|
||||
val text = remember(transcodingTiers, duration) {
|
||||
val seconds = duration.inWholeSeconds
|
||||
val byteCountEstimate = TranscodingQuality.createFromQualityTiers(transcodingTiers, duration.inWholeMilliseconds).byteCountEstimate
|
||||
|
||||
String.format(Locale.getDefault(), "%d:%02d • %s", seconds / 60, seconds % 60, byteCountEstimate.bytes.toUnitString())
|
||||
"${duration.formatAsClock()} • ${byteCountEstimate.bytes.toUnitString()}"
|
||||
}
|
||||
|
||||
Text(
|
||||
|
||||
+59
-21
@@ -58,6 +58,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -94,11 +95,14 @@ import org.signal.core.ui.compose.list.rememberReorderBuffer
|
||||
import org.signal.core.ui.compose.list.rememberReorderableListState
|
||||
import org.signal.core.ui.compose.list.reorderableList
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.signal.core.util.ContentTypeUtil
|
||||
import org.signal.glide.compose.GlideImage
|
||||
import org.signal.mediasend.R
|
||||
import org.signal.mediasend.screens.MediaSendMetrics
|
||||
import org.signal.mediasend.screens.edit.rememberPreviewMedia
|
||||
import org.signal.mediasend.test.TestTags
|
||||
import org.signal.mediasend.util.formatAsClock
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import org.signal.core.ui.permissions.Permissions as PermissionsUtil
|
||||
|
||||
/** How many empty tiles stand in for the gallery we are not allowed to show. Matches the v2 gallery. */
|
||||
@@ -568,8 +572,12 @@ private fun MediaTile(
|
||||
}
|
||||
)
|
||||
|
||||
val outerCornerClip by animateDpAsState(
|
||||
if (selectionIndex >= 0) 0.dp else 2.dp
|
||||
)
|
||||
|
||||
val cornerClip by animateDpAsState(
|
||||
if (selectionIndex >= 0) 12.dp else 0.dp
|
||||
if (selectionIndex >= 0) 12.dp else 2.dp
|
||||
)
|
||||
|
||||
// Square regardless of what is in it. The thumbnail emits nothing until it has loaded, so a tile sized by its content
|
||||
@@ -578,33 +586,37 @@ private fun MediaTile(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
.background(color = MaterialTheme.colorScheme.surfaceVariant)
|
||||
.background(color = MaterialTheme.colorScheme.surfaceVariant, shape = RoundedCornerShape(outerCornerClip))
|
||||
.clickable(
|
||||
onClick = { onEvent(MediaSelectScreenEvents.MediaClick(media)) },
|
||||
onClickLabel = media.fileName,
|
||||
role = Role.Button
|
||||
)
|
||||
) {
|
||||
if (LocalInspectionMode.current) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.scale(scale)
|
||||
.background(color = Previews.rememberRandomColor(), shape = RoundedCornerShape(cornerClip))
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
)
|
||||
} else {
|
||||
val width = maxWidth
|
||||
val height = maxHeight
|
||||
val width = maxWidth
|
||||
val height = maxHeight
|
||||
|
||||
GlideImage(
|
||||
model = media.uri,
|
||||
imageSize = DpSize(width, height),
|
||||
modifier = Modifier
|
||||
.aspectRatio(1f)
|
||||
.scale(scale)
|
||||
.clip(RoundedCornerShape(cornerClip))
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.aspectRatio(1f)
|
||||
.scale(scale)
|
||||
.clip(RoundedCornerShape(cornerClip))
|
||||
) {
|
||||
if (LocalInspectionMode.current) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = Previews.rememberRandomColor())
|
||||
.fillMaxWidth()
|
||||
)
|
||||
} else {
|
||||
GlideImage(
|
||||
model = media.uri,
|
||||
imageSize = DpSize(width, height),
|
||||
modifier = Modifier.aspectRatio(1f)
|
||||
)
|
||||
}
|
||||
|
||||
MediaTileVideoOverlay(media)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,6 +642,32 @@ private fun MediaTile(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MediaTileVideoOverlay(
|
||||
media: Media
|
||||
) {
|
||||
if (ContentTypeUtil.isVideo(media.contentType) && !media.isVideoGif) {
|
||||
Box(
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
0f to Color.Black.copy(alpha = 0.4f),
|
||||
1f to Color.Transparent
|
||||
)
|
||||
)
|
||||
.padding(top = 8.dp, end = 8.dp, bottom = 26.dp)
|
||||
) {
|
||||
Text(
|
||||
text = remember(media.duration) { media.duration.milliseconds.formatAsClock() },
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = SignalTheme.colors.colorOnCustom
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NextButton(mediaSelectionCount: Int, recipientChatColor: Color? = null, onClick: () -> Unit) {
|
||||
Buttons.MediumTonal(
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.mediasend.util
|
||||
|
||||
import java.util.Locale
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* Renders a media duration as a clock, e.g. "0:04" or "1:02:03". Hours are dropped below an hour so the
|
||||
* short clips that dominate the send flow read as minutes and seconds.
|
||||
*/
|
||||
internal fun Duration.formatAsClock(): String {
|
||||
return toComponents { hours, minutes, seconds, _ ->
|
||||
if (hours > 0) {
|
||||
String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, seconds)
|
||||
} else {
|
||||
String.format(Locale.getDefault(), "%d:%02d", minutes, seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user