Skip to content

Instantly share code, notes, and snippets.

@Kyriakos-Georgiopoulos
Created December 31, 2025 14:38
Show Gist options
  • Select an option

  • Save Kyriakos-Georgiopoulos/6f084ce2fdde17f4785a2bb7fc9f46e3 to your computer and use it in GitHub Desktop.

Select an option

Save Kyriakos-Georgiopoulos/6f084ce2fdde17f4785a2bb7fc9f46e3 to your computer and use it in GitHub Desktop.
/*
* Copyright 2025 Kyriakos Georgiopoulos
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccessTime
import androidx.compose.material.icons.outlined.ChevronRight
import androidx.compose.material.icons.outlined.DirectionsCar
import androidx.compose.material.icons.outlined.EnergySavingsLeaf
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material.icons.outlined.FlightTakeoff
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Key
import androidx.compose.material.icons.outlined.LocalShipping
import androidx.compose.material.icons.outlined.PersonOutline
import androidx.compose.material.icons.outlined.Place
import androidx.compose.material.icons.outlined.ReceiptLong
import androidx.compose.material.icons.outlined.Restaurant
import androidx.compose.material.icons.outlined.Schedule
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.StarBorder
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.zengrip.R
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private enum class SplashStage {
Logo,
LogoToLines,
LinesGrow,
Centering,
RevealSlit,
RevealFull,
Done
}
private object TwinLinesTweens {
val Grow = tween<Float>(780, easing = CubicBezierEasing(0.18f, 0f, 0f, 1f))
val Center = tween<Float>(700, easing = CubicBezierEasing(0.18f, 0f, 0f, 1f))
val Slit = tween<Float>(980, easing = CubicBezierEasing(0.18f, 0f, 0f, 1f))
val FullOpen = tween<Float>(2600, easing = CubicBezierEasing(0.10f, 0.92f, 0.16f, 1f))
val LogoInAlpha = tween<Float>(920, easing = CubicBezierEasing(0.12f, 1f, 0.22f, 1f))
val LogoInScale = tween<Float>(1100, easing = CubicBezierEasing(0.12f, 1f, 0.22f, 1f))
val LogoOut = tween<Float>(260, easing = LinearEasing)
}
/**
* Splash-to-home reveal animation using two vertical "twin lines":
* - Logo fades in/out
* - Two lines appear near the center, grow to full height
* - Lines slide to the true center
* - A black curtain opens by separating the lines until the screen is fully revealed
*
* The home content is always rendered behind the curtain (full size).
*
* Tap anywhere after the reveal is complete to run the reverse-close and replay the animation.
*/
@Composable
fun TwinLinesRevealDemo(
modifier: Modifier = Modifier,
lineWidth: Dp = 3.dp,
lineMinGap: Dp = 10.dp,
lineMaxGap: Dp = 18.dp,
) {
var stage by remember { mutableStateOf(SplashStage.Logo) }
val gapPxAnim = remember { Animatable(0f) }
val linesHeightFrac = remember { Animatable(0f) }
val linesAlpha = remember { Animatable(0f) }
val centerOffsetFracAnim = remember { Animatable(1f) }
var screenWidthPxState by remember { mutableStateOf(0f) }
val logoAlphaAnim = remember { Animatable(0f) }
val logoScaleAnim = remember { Animatable(0.92f) }
var replayKey by remember { mutableStateOf(0) }
var isAnimating by remember { mutableStateOf(false) }
val uiScope = rememberCoroutineScope()
val density = LocalDensity.current
val transition = updateTransition(targetState = stage, label = "splash")
val lineGapDp by transition.animateDp(
label = "lineGapDp",
transitionSpec = { tween(260, easing = FastOutSlowInEasing) }
) { s ->
when (s) {
SplashStage.Logo, SplashStage.LogoToLines -> lineMinGap
else -> lineMaxGap
}
}
val lineWidthPx = with(density) { lineWidth.toPx() }
val lineGapPx = with(density) { lineGapDp.toPx() }
fun smootherStep(t: Float): Float {
val x = t.coerceIn(0f, 1f)
return x * x * x * (x * (x * 6f - 15f) + 10f)
}
suspend fun resetToStartState() {
linesHeightFrac.snapTo(0f)
linesAlpha.snapTo(0f)
gapPxAnim.snapTo(0f)
centerOffsetFracAnim.snapTo(1f)
logoAlphaAnim.snapTo(0f)
logoScaleAnim.snapTo(0.86f)
stage = SplashStage.Logo
}
suspend fun playForward() = coroutineScope {
resetToStartState()
// Give the reverse-close a moment to "land" in pure black before re-introducing motion
withFrameNanos { }
delay(180)
withFrameNanos { }
// Logo in: slower + more "premium"
launch { logoAlphaAnim.animateTo(1f, TwinLinesTweens.LogoInAlpha) }
launch { logoScaleAnim.animateTo(1f, TwinLinesTweens.LogoInScale) }
// Keep logo readable a bit longer before transitioning to lines
delay(820)
stage = SplashStage.LogoToLines
// Logo out
launch { logoAlphaAnim.animateTo(0f, TwinLinesTweens.LogoOut) }
launch { logoScaleAnim.animateTo(0.93f, TwinLinesTweens.LogoOut) }
// Lines in
linesAlpha.animateTo(1f, tween(300, easing = LinearEasing))
delay(220)
stage = SplashStage.LinesGrow
linesHeightFrac.animateTo(1f, TwinLinesTweens.Grow)
stage = SplashStage.Centering
val slitOverlapMs = 220L
val centeringJob = launch { centerOffsetFracAnim.animateTo(0f, TwinLinesTweens.Center) }
delay((TwinLinesTweens.Center.durationMillis - slitOverlapMs).coerceAtLeast(0).toLong())
stage = SplashStage.RevealSlit
gapPxAnim.snapTo(0f)
gapPxAnim.animateTo(lineGapPx, TwinLinesTweens.Slit)
centeringJob.join()
stage = SplashStage.RevealFull
while (screenWidthPxState <= 0f) delay(16)
gapPxAnim.animateTo(screenWidthPxState, TwinLinesTweens.FullOpen)
delay(160)
linesAlpha.animateTo(0f, tween(380, easing = LinearEasing))
stage = SplashStage.Done
}
suspend fun closeAndReplay() = coroutineScope {
if (isAnimating) return@coroutineScope
isAnimating = true
while (screenWidthPxState <= 0f) delay(16)
// Ensure we're in a clean "fully revealed" state before reversing
stage = SplashStage.RevealFull
// Keep logo hidden during reverse so it can't flash
logoAlphaAnim.snapTo(0f)
logoScaleAnim.snapTo(0.86f)
// Lines should be visible, full height, centered
linesAlpha.snapTo(1f)
linesHeightFrac.snapTo(1f)
centerOffsetFracAnim.snapTo(0f)
// Start from fully open curtain
gapPxAnim.snapTo(screenWidthPxState)
val closeCurtainTween = tween<Float>(
durationMillis = 900,
easing = CubicBezierEasing(0.20f, 0f, 0.0f, 1f)
)
val moveOffCenterTween = tween<Float>(
durationMillis = 650,
easing = CubicBezierEasing(0.18f, 0f, 0.0f, 1f)
)
val shrinkHeightTween = tween<Float>(
durationMillis = 780,
easing = CubicBezierEasing(0.18f, 0f, 0.0f, 1f)
)
// 1) Close curtains first (lines stay centered and full height)
gapPxAnim.animateTo(0f, closeCurtainTween)
// Make sure we are truly "closed"
stage = SplashStage.Centering
// 2) Move off-center (back to the initial offset)
centerOffsetFracAnim.animateTo(1f, moveOffCenterTween)
// 3) Reduce height smoothly to 0
linesHeightFrac.animateTo(0f, shrinkHeightTween)
// Now hide lines (subtle, not a pop)
linesAlpha.animateTo(0f, tween(180, easing = LinearEasing))
// Reset stage so the next forward run starts logically from Logo.
stage = SplashStage.Logo
// Give a little "restart breath" (black stays black)
withFrameNanos { }
delay(220)
isAnimating = false
replayKey++
}
LaunchedEffect(replayKey) {
isAnimating = true
playForward()
isAnimating = false
}
BoxWithConstraints(modifier = modifier.fillMaxSize()) {
val screenWidthPx = with(density) { maxWidth.toPx() }
LaunchedEffect(screenWidthPx) { screenWidthPxState = screenWidthPx }
val centerOffsetFrac = centerOffsetFracAnim.value
val rawT = (gapPxAnim.value / screenWidthPx).coerceIn(0f, 1f)
val t = smootherStep(rawT)
val openingPxEased = t * screenWidthPx
val minHalfSeparation = (lineWidthPx * 1.3f).coerceAtLeast(2.5f)
val halfSeparation = maxOf(openingPxEased / 2f, minHalfSeparation)
MockHomeScreen()
RevealCurtainOverlay(
openingPx = openingPxEased,
centerOffsetFrac = centerOffsetFrac,
isRevealing = stage >= SplashStage.RevealSlit,
edgeFeatherPx = with(density) { 34.dp.toPx() }
)
Box(
modifier = Modifier
.align(Alignment.Center)
.graphicsLayer {
scaleX = logoScaleAnim.value
scaleY = logoScaleAnim.value
}
.alpha(logoAlphaAnim.value)
) {
KyriakosLogo()
}
Canvas(
Modifier
.fillMaxSize()
.alpha(linesAlpha.value)
) {
val heightFrac = linesHeightFrac.value.coerceIn(0f, 1f)
if (heightFrac <= 0f) return@Canvas
val h = size.height
val w = size.width
val cx = (w / 2f) + (w * 0.14f * centerOffsetFrac)
val halfHeight = (h * heightFrac) / 2f
val top = (h / 2f) - halfHeight
val bottom = (h / 2f) + halfHeight
val x1 = cx - halfSeparation
val x2 = cx + halfSeparation
drawLine(Color.White, Offset(x1, top), Offset(x1, bottom), strokeWidth = lineWidthPx)
drawLine(Color.White, Offset(x2, top), Offset(x2, bottom), strokeWidth = lineWidthPx)
}
Box(
modifier = Modifier
.fillMaxSize()
.pointerInput(stage, isAnimating) {
detectTapGestures {
if (stage == SplashStage.Done && !isAnimating) {
uiScope.launch { closeAndReplay() }
}
}
}
)
}
}
/** Logo shown during the splash stage. */
@Composable
private fun KyriakosLogo(modifier: Modifier = Modifier) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "K",
color = Color.White,
style = MaterialTheme.typography.displayLarge.copy(fontWeight = FontWeight.Black)
)
Spacer(Modifier.height(6.dp))
Text(
text = "Kyriakos",
color = Color.White,
style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.SemiBold,
letterSpacing = MaterialTheme.typography.headlineMedium.letterSpacing
)
)
}
}
/** Mock home screen content rendered behind the reveal curtain. */
@Composable
private fun MockHomeScreen() {
Surface(color = Color(0xFFF4F5F7)) {
Column(
Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.statusBars)
) {
HeaderPromo()
Spacer(Modifier.height(6.dp))
QuickActions()
Spacer(Modifier.height(6.dp))
SearchRow()
SectionTitle("Around you")
FeedList()
Spacer(Modifier.weight(1f))
BottomNav()
}
}
}
@Composable
private fun HeaderPromo() {
Surface(
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 12.dp)
.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
color = Color(0xFF0F5A3C)
) {
Row(Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f)) {
Text(
text = "Try Eco Mode",
color = Color.White,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold
)
Spacer(Modifier.height(4.dp))
Text(
text = "Go greener →",
color = Color.White.copy(alpha = 0.9f),
style = MaterialTheme.typography.bodyMedium
)
}
Box(
Modifier
.size(44.dp)
.clip(CircleShape)
.background(Color.White.copy(alpha = 0.16f)),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Outlined.EnergySavingsLeaf,
contentDescription = null,
tint = Color.White
)
}
}
}
}
@Composable
private fun QuickActions() {
val items = listOf(
"Ride" to Icons.Outlined.DirectionsCar,
"Food" to Icons.Outlined.Restaurant,
"Rent" to Icons.Outlined.Key,
"Reserve" to Icons.Outlined.Schedule,
"Courier" to Icons.Outlined.LocalShipping,
"Travel" to Icons.Outlined.FlightTakeoff
)
Column(Modifier.padding(horizontal = 16.dp)) {
repeat(2) { row ->
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
repeat(3) { col ->
val idx = row * 3 + col
QuickActionCard(
title = items[idx].first,
icon = items[idx].second,
modifier = Modifier.weight(1f)
)
}
}
Spacer(Modifier.height(10.dp))
}
}
}
/** Small quick-action card used in the home grid. */
@Composable
private fun QuickActionCard(
title: String,
icon: androidx.compose.ui.graphics.vector.ImageVector,
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier.height(86.dp),
shape = RoundedCornerShape(14.dp),
color = Color.White
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(12.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
Modifier
.size(34.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color(0xFFF1F2F4)),
contentAlignment = Alignment.Center
) {
Icon(icon, contentDescription = null, tint = Color(0xFF111111))
}
Spacer(Modifier.height(10.dp))
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium,
color = Color(0xFF111111),
textAlign = TextAlign.Center
)
}
}
}
@Composable
private fun SearchRow() {
Row(
Modifier
.padding(horizontal = 16.dp, vertical = 12.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Surface(
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(14.dp),
color = Color.White
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Outlined.Search, contentDescription = null, tint = Color(0xFF6B7280))
Spacer(Modifier.width(8.dp))
Text(
"Where to?",
color = Color(0xFF6B7280),
style = MaterialTheme.typography.bodyMedium
)
}
}
Surface(shape = RoundedCornerShape(14.dp), color = Color.White) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Outlined.AccessTime, contentDescription = null, tint = Color(0xFF111111))
Spacer(Modifier.width(6.dp))
Text(
"Now",
color = Color(0xFF111111),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium
)
Spacer(Modifier.width(4.dp))
Icon(Icons.Outlined.ExpandMore, contentDescription = null, tint = Color(0xFF111111))
}
}
}
}
@Composable
private fun SectionTitle(text: String) {
Text(
text = text,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF111111)
)
}
@Composable
private fun FeedList() {
Column(Modifier.padding(horizontal = 16.dp)) {
FeedRow(Icons.Outlined.StarBorder, "Choose a saved place", "Home, Work and more")
Spacer(Modifier.height(10.dp))
FeedRow(Icons.Outlined.Place, "Set destination on map", "Pick a spot precisely")
Spacer(Modifier.height(12.dp))
GoogleMapPreview()
}
}
@Composable
private fun FeedRow(
icon: androidx.compose.ui.graphics.vector.ImageVector,
title: String,
subtitle: String
) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
color = Color.White
) {
Row(Modifier.padding(14.dp), verticalAlignment = Alignment.CenterVertically) {
Box(
Modifier
.size(40.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFF1F2F4)),
contentAlignment = Alignment.Center
) {
Icon(icon, contentDescription = null, tint = Color(0xFF111111))
}
Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f)) {
Text(title, color = Color(0xFF111111), fontWeight = FontWeight.Medium)
Spacer(Modifier.height(2.dp))
Text(
subtitle,
color = Color(0xFF6B7280),
style = MaterialTheme.typography.bodyMedium
)
}
Icon(Icons.Outlined.ChevronRight, contentDescription = null, tint = Color(0xFF9AA0AA))
}
}
}
@Composable
private fun BottomNav() {
NavigationBar(
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding(),
containerColor = Color.White,
tonalElevation = 4.dp
) {
val colors = NavigationBarItemDefaults.colors(
selectedIconColor = Color(0xFF111111),
selectedTextColor = Color(0xFF111111),
unselectedIconColor = Color(0xFF8A8F98),
unselectedTextColor = Color(0xFF8A8F98),
indicatorColor = Color.Transparent
)
NavigationBarItem(
selected = true,
onClick = {},
icon = { Icon(Icons.Outlined.Home, contentDescription = null) },
label = { Text("Home") },
alwaysShowLabel = true,
colors = colors
)
NavigationBarItem(
selected = false,
onClick = {},
icon = { Icon(Icons.Outlined.ReceiptLong, contentDescription = null) },
label = { Text("Activity") },
alwaysShowLabel = true,
colors = colors
)
NavigationBarItem(
selected = false,
onClick = {},
icon = { Icon(Icons.Outlined.PersonOutline, contentDescription = null) },
label = { Text("Account") },
alwaysShowLabel = true,
colors = colors
)
}
}
/**
* Black curtain overlay with a centered "window" whose width is controlled by [openingPx].
*
* When [openingPx] reaches (almost) the screen width, the overlay stops drawing to avoid edge artifacts.
*/
@Composable
private fun RevealCurtainOverlay(
openingPx: Float,
centerOffsetFrac: Float,
isRevealing: Boolean,
modifier: Modifier = Modifier,
startOffsetFractionOfWidth: Float = 0.14f,
edgeFeatherPx: Float = 0f
) {
Canvas(modifier.fillMaxSize()) {
val w = size.width
val h = size.height
if (!isRevealing) {
drawRect(Color.Black)
return@Canvas
}
val windowW = openingPx.coerceIn(0f, w)
if (windowW >= w - 1f) return@Canvas
if (windowW <= 0f) {
drawRect(Color.Black)
return@Canvas
}
val cx = (w / 2f) + (w * startOffsetFractionOfWidth * centerOffsetFrac)
val leftEdge = (cx - windowW / 2f).coerceIn(0f, w)
val rightEdge = (cx + windowW / 2f).coerceIn(0f, w)
if (leftEdge > 0f) {
drawRect(
color = Color.Black,
topLeft = Offset.Zero,
size = Size(leftEdge, h)
)
}
if (rightEdge < w) {
drawRect(
color = Color.Black,
topLeft = Offset(rightEdge, 0f),
size = Size(w - rightEdge, h)
)
}
val feather = edgeFeatherPx
.coerceIn(0f, 120f)
.coerceAtMost(windowW / 2f)
.coerceAtMost(leftEdge)
.coerceAtMost(w - rightEdge)
if (feather > 0f) {
drawRect(
brush = Brush.horizontalGradient(
0f to Color.Black,
1f to Color.Transparent
),
topLeft = Offset(leftEdge - feather, 0f),
size = Size(feather, h)
)
drawRect(
brush = Brush.horizontalGradient(
0f to Color.Transparent,
1f to Color.Black
),
topLeft = Offset(rightEdge, 0f),
size = Size(feather, h)
)
}
}
}
/** Static Google-style map preview card (uses a local drawable). */
@Composable
private fun GoogleMapPreview(modifier: Modifier = Modifier) {
Surface(
modifier = modifier
.fillMaxWidth()
.height(240.dp)
.padding(horizontal = 4.dp, vertical = 8.dp),
shape = RoundedCornerShape(20.dp),
tonalElevation = 0.dp
) {
Image(
painter = painterResource(id = R.drawable.map_preview),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(20.dp)),
contentScale = ContentScale.Crop
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment