mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-17 15:32:01 +00:00
integrate custom controls for player
uniform UI/UX and topbar visibility state is not detached from controls visibility
This commit is contained in:
parent
504ed2350a
commit
2b6b0dd57f
1 changed files with 290 additions and 29 deletions
|
|
@ -5,24 +5,31 @@ import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Close
|
import androidx.compose.material.icons.rounded.Close
|
||||||
|
import androidx.compose.material.icons.rounded.Forward10
|
||||||
|
import androidx.compose.material.icons.rounded.Pause
|
||||||
|
import androidx.compose.material.icons.rounded.PlayArrow
|
||||||
|
import androidx.compose.material.icons.rounded.Replay10
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -35,10 +42,11 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.pointer.PointerEventPass
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
|
@ -46,17 +54,25 @@ import com.nuvio.app.core.ui.PlatformBackHandler
|
||||||
import com.nuvio.app.features.player.EnterImmersivePlayerMode
|
import com.nuvio.app.features.player.EnterImmersivePlayerMode
|
||||||
import com.nuvio.app.features.player.LockPlayerToLandscape
|
import com.nuvio.app.features.player.LockPlayerToLandscape
|
||||||
import com.nuvio.app.features.player.PlatformPlayerSurface
|
import com.nuvio.app.features.player.PlatformPlayerSurface
|
||||||
|
import com.nuvio.app.features.player.PlayerEngineController
|
||||||
|
import com.nuvio.app.features.player.PlayerLayoutMetrics
|
||||||
|
import com.nuvio.app.features.player.PlayerPlaybackSnapshot
|
||||||
import com.nuvio.app.features.player.PlayerResizeMode
|
import com.nuvio.app.features.player.PlayerResizeMode
|
||||||
|
import com.nuvio.app.features.player.formatPlaybackTime
|
||||||
|
import com.nuvio.app.features.player.playerHorizontalSafePadding
|
||||||
import com.nuvio.app.features.trailer.TrailerPlaybackSource
|
import com.nuvio.app.features.trailer.TrailerPlaybackSource
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import nuvio.composeapp.generated.resources.Res
|
import nuvio.composeapp.generated.resources.Res
|
||||||
import nuvio.composeapp.generated.resources.action_retry
|
import nuvio.composeapp.generated.resources.action_retry
|
||||||
|
import nuvio.composeapp.generated.resources.compose_player_seek_back_10
|
||||||
|
import nuvio.composeapp.generated.resources.compose_player_seek_forward_10
|
||||||
import nuvio.composeapp.generated.resources.detail_tab_trailer
|
import nuvio.composeapp.generated.resources.detail_tab_trailer
|
||||||
import nuvio.composeapp.generated.resources.trailer_close
|
import nuvio.composeapp.generated.resources.trailer_close
|
||||||
import nuvio.composeapp.generated.resources.trailer_unable_to_play
|
import nuvio.composeapp.generated.resources.trailer_unable_to_play
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
private const val TrailerControlsAutoHideMs = 3500L
|
private const val TrailerControlsAutoHideMs = 3500L
|
||||||
|
private const val TrailerSeekStepMs = 10_000L
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TrailerPlayer(
|
fun TrailerPlayer(
|
||||||
|
|
@ -125,26 +141,34 @@ private fun FullscreenTrailerPlayerContent(
|
||||||
}
|
}
|
||||||
val activeError = errorMessage ?: playerError
|
val activeError = errorMessage ?: playerError
|
||||||
|
|
||||||
|
var playerController by remember(playbackSource?.videoUrl) {
|
||||||
|
mutableStateOf<PlayerEngineController?>(null)
|
||||||
|
}
|
||||||
|
var playbackSnapshot by remember(playbackSource?.videoUrl) {
|
||||||
|
mutableStateOf(PlayerPlaybackSnapshot())
|
||||||
|
}
|
||||||
|
var scrubbingPositionMs by remember(playbackSource?.videoUrl) {
|
||||||
|
mutableStateOf<Long?>(null)
|
||||||
|
}
|
||||||
|
val displayedPositionMs = scrubbingPositionMs ?: playbackSnapshot.positionMs
|
||||||
|
|
||||||
var controlsVisible by remember { mutableStateOf(false) }
|
var controlsVisible by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(controlsVisible, isLoading, activeError, playbackSource) {
|
LaunchedEffect(controlsVisible, playbackSnapshot.isPlaying, isLoading, activeError, playbackSource) {
|
||||||
if (!controlsVisible || isLoading || activeError != null || playbackSource == null) {
|
if (!controlsVisible || !playbackSnapshot.isPlaying || isLoading || activeError != null || playbackSource == null) {
|
||||||
return@LaunchedEffect
|
return@LaunchedEffect
|
||||||
}
|
}
|
||||||
delay(TrailerControlsAutoHideMs)
|
delay(TrailerControlsAutoHideMs)
|
||||||
controlsVisible = false
|
controlsVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
BoxWithConstraints(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Color.Black)
|
.background(Color.Black),
|
||||||
.pointerInput(Unit) {
|
|
||||||
awaitEachGesture {
|
|
||||||
awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
|
|
||||||
controlsVisible = !controlsVisible
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
|
val metrics = remember(maxWidth) { PlayerLayoutMetrics.fromWidth(maxWidth) }
|
||||||
|
val horizontalSafePadding = playerHorizontalSafePadding()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
isLoading -> {
|
isLoading -> {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
|
|
@ -182,6 +206,13 @@ private fun FullscreenTrailerPlayerContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackSource != null -> {
|
playbackSource != null -> {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures(onTap = { controlsVisible = !controlsVisible })
|
||||||
|
},
|
||||||
|
) {
|
||||||
PlatformPlayerSurface(
|
PlatformPlayerSurface(
|
||||||
sourceUrl = playbackSource.videoUrl,
|
sourceUrl = playbackSource.videoUrl,
|
||||||
sourceAudioUrl = playbackSource.audioUrl,
|
sourceAudioUrl = playbackSource.audioUrl,
|
||||||
|
|
@ -189,37 +220,119 @@ private fun FullscreenTrailerPlayerContent(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
playWhenReady = true,
|
playWhenReady = true,
|
||||||
resizeMode = PlayerResizeMode.Fit,
|
resizeMode = PlayerResizeMode.Fit,
|
||||||
useNativeController = true,
|
useNativeController = false,
|
||||||
onControllerReady = {},
|
onControllerReady = { playerController = it },
|
||||||
onSnapshot = {},
|
onSnapshot = { playbackSnapshot = it },
|
||||||
onError = { playerError = it },
|
onError = { playerError = it },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (playbackSnapshot.isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = controlsVisible || isLoading || activeError != null,
|
visible = controlsVisible || isLoading || activeError != null,
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
modifier = Modifier.align(Alignment.TopStart),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
TrailerTopBar(
|
ControlsOverlay(
|
||||||
title = headerType,
|
title = headerType,
|
||||||
subtitle = headerSubtitle,
|
subtitle = headerSubtitle,
|
||||||
|
playbackSnapshot = playbackSnapshot,
|
||||||
|
displayedPositionMs = displayedPositionMs,
|
||||||
|
metrics = metrics,
|
||||||
|
horizontalSafePadding = horizontalSafePadding,
|
||||||
|
hasPlaybackSurface = playbackSource != null && activeError == null,
|
||||||
onClose = onDismiss,
|
onClose = onDismiss,
|
||||||
|
onTogglePlayback = {
|
||||||
|
val controller = playerController ?: return@ControlsOverlay
|
||||||
|
if (playbackSnapshot.isPlaying) controller.pause() else controller.play()
|
||||||
|
},
|
||||||
|
onSeekBack = { playerController?.seekBy(-TrailerSeekStepMs) },
|
||||||
|
onSeekForward = { playerController?.seekBy(TrailerSeekStepMs) },
|
||||||
|
onScrubChange = { scrubbingPositionMs = it },
|
||||||
|
onScrubFinished = {
|
||||||
|
val controller = playerController
|
||||||
|
val target = scrubbingPositionMs
|
||||||
|
scrubbingPositionMs = null
|
||||||
|
if (controller != null && target != null) {
|
||||||
|
controller.seekTo(target)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TrailerTopBar(
|
private fun ControlsOverlay(
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
playbackSnapshot: PlayerPlaybackSnapshot,
|
||||||
|
displayedPositionMs: Long,
|
||||||
|
metrics: PlayerLayoutMetrics,
|
||||||
|
horizontalSafePadding: Dp,
|
||||||
|
hasPlaybackSurface: Boolean,
|
||||||
|
onClose: () -> Unit,
|
||||||
|
onTogglePlayback: () -> Unit,
|
||||||
|
onSeekBack: () -> Unit,
|
||||||
|
onSeekForward: () -> Unit,
|
||||||
|
onScrubChange: (Long) -> Unit,
|
||||||
|
onScrubFinished: () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
TopBar(
|
||||||
|
title = title,
|
||||||
|
subtitle = subtitle,
|
||||||
|
onClose = onClose,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopStart)
|
||||||
|
.padding(horizontal = horizontalSafePadding),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasPlaybackSurface) {
|
||||||
|
CenterControls(
|
||||||
|
snapshot = playbackSnapshot,
|
||||||
|
metrics = metrics,
|
||||||
|
onSeekBack = onSeekBack,
|
||||||
|
onSeekForward = onSeekForward,
|
||||||
|
onTogglePlayback = onTogglePlayback,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.padding(bottom = metrics.centerLift),
|
||||||
|
)
|
||||||
|
|
||||||
|
ProgressBar(
|
||||||
|
playbackSnapshot = playbackSnapshot,
|
||||||
|
displayedPositionMs = displayedPositionMs,
|
||||||
|
metrics = metrics,
|
||||||
|
onScrubChange = onScrubChange,
|
||||||
|
onScrubFinished = onScrubFinished,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = horizontalSafePadding + metrics.horizontalPadding)
|
||||||
|
.padding(bottom = metrics.sliderBottomOffset),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TopBar(
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String,
|
subtitle: String,
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(
|
.background(
|
||||||
Color.Black.copy(alpha = 0.55f),
|
Color.Black.copy(alpha = 0.55f),
|
||||||
|
|
@ -271,3 +384,151 @@ private fun TrailerTopBar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ProgressBar(
|
||||||
|
playbackSnapshot: PlayerPlaybackSnapshot,
|
||||||
|
displayedPositionMs: Long,
|
||||||
|
metrics: PlayerLayoutMetrics,
|
||||||
|
onScrubChange: (Long) -> Unit,
|
||||||
|
onScrubFinished: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val durationMs = playbackSnapshot.durationMs.coerceAtLeast(1L)
|
||||||
|
val clampedPosition = displayedPositionMs.coerceIn(0L, durationMs)
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
Slider(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(metrics.sliderTouchHeight)
|
||||||
|
.graphicsLayer(scaleY = metrics.sliderScaleY),
|
||||||
|
value = clampedPosition.toFloat(),
|
||||||
|
onValueChange = { onScrubChange(it.toLong()) },
|
||||||
|
onValueChangeFinished = onScrubFinished,
|
||||||
|
valueRange = 0f..durationMs.toFloat(),
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 14.dp)
|
||||||
|
.padding(top = 4.dp, bottom = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
TimePill(text = formatPlaybackTime(clampedPosition), fontSize = metrics.timeSize)
|
||||||
|
TimePill(text = formatPlaybackTime(durationMs), fontSize = metrics.timeSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CenterControls(
|
||||||
|
snapshot: PlayerPlaybackSnapshot,
|
||||||
|
metrics: PlayerLayoutMetrics,
|
||||||
|
onSeekBack: () -> Unit,
|
||||||
|
onSeekForward: () -> Unit,
|
||||||
|
onTogglePlayback: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(metrics.centerGap),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
SideControlButton(
|
||||||
|
icon = Icons.Rounded.Replay10,
|
||||||
|
contentDescription = stringResource(Res.string.compose_player_seek_back_10),
|
||||||
|
metrics = metrics,
|
||||||
|
onClick = onSeekBack,
|
||||||
|
)
|
||||||
|
PlayPauseControlButton(
|
||||||
|
isPlaying = snapshot.isPlaying,
|
||||||
|
isBuffering = snapshot.isLoading,
|
||||||
|
metrics = metrics,
|
||||||
|
onClick = onTogglePlayback,
|
||||||
|
)
|
||||||
|
SideControlButton(
|
||||||
|
icon = Icons.Rounded.Forward10,
|
||||||
|
contentDescription = stringResource(Res.string.compose_player_seek_forward_10),
|
||||||
|
metrics = metrics,
|
||||||
|
onClick = onSeekForward,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SideControlButton(
|
||||||
|
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||||
|
contentDescription: String?,
|
||||||
|
metrics: PlayerLayoutMetrics,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(metrics.sideButtonPadding),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(metrics.sideIconSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PlayPauseControlButton(
|
||||||
|
isPlaying: Boolean,
|
||||||
|
isBuffering: Boolean,
|
||||||
|
metrics: PlayerLayoutMetrics,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(metrics.playButtonPadding),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
if (isBuffering) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 3.dp,
|
||||||
|
modifier = Modifier.size(metrics.playIconSize),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isPlaying) Icons.Rounded.Pause else Icons.Rounded.PlayArrow,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(metrics.playIconSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TimePill(
|
||||||
|
text: String,
|
||||||
|
fontSize: androidx.compose.ui.unit.TextUnit,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.background(Color.Black.copy(alpha = 0.5f))
|
||||||
|
.border(1.dp, Color.White.copy(alpha = 0.2f), RoundedCornerShape(12.dp))
|
||||||
|
.padding(horizontal = 10.dp, vertical = 4.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.labelSmall.copy(
|
||||||
|
fontSize = fontSize,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
),
|
||||||
|
color = Color.White,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue