Coverage Summary for Class: SurferBottomSheetContentKt (com.georgeci.moneysurfer.uikit.components)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| SurferBottomSheetContentKt |
100%
(1/1)
|
50%
(1/2)
|
23.1%
(6/26)
|
66.1%
(37/56)
|
69.2%
(460/665)
|
package com.georgeci.moneysurfer.uikit.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.georgeci.moneysurfer.uikit.icons.SurferIcons
import com.georgeci.moneysurfer.uikit.theme.AppTheme
import moneysurfer.uikit.generated.resources.Res
import moneysurfer.uikit.generated.resources.uikit_close
import org.jetbrains.compose.resources.stringResource
/**
* Inner scaffold for a navigation-driven bottom sheet entry: a title, an optional subtitle, a
* body slot, and an optional action slot pinned at the bottom. The surrounding
* [androidx.compose.material3.ModalBottomSheet] (handle, container, scrim) is the caller's
* responsibility — typically provided by the navigation scene strategy.
*
* [onClose] adds the circular dismiss button on the title row. Sheets reached through navigation
* do not need it — the scrim and the back gesture already dismiss them — but a picker opened over
* a form does, which is why it is opt-in rather than always present.
*/
@Composable
fun SurferBottomSheetContent(
title: String,
modifier: Modifier = Modifier,
subtitle: String? = null,
contentPadding: PaddingValues = PaddingValues(horizontal = 20.dp),
onClose: (() -> Unit)? = null,
button: @Composable (() -> Unit)? = null,
content: @Composable ColumnScope.() -> Unit = {},
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
) {
Column(modifier = Modifier.padding(contentPadding)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = title,
style = AppTheme.typography.titleMedium,
color = AppTheme.materialColors.onSurface,
modifier = Modifier.weight(1f),
)
if (onClose != null) {
CloseButton(onClose)
}
}
if (subtitle != null) {
Spacer(Modifier.height(4.dp))
Text(
text = subtitle,
style = AppTheme.typography.bodySmall,
color = AppTheme.materialColors.onSurfaceVariant,
)
}
}
Spacer(Modifier.height(12.dp))
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
content = content,
)
if (button != null) {
Spacer(Modifier.height(16.dp))
Column(modifier = Modifier.padding(contentPadding)) {
button()
}
}
}
}
@Composable
private fun CloseButton(onClose: () -> Unit) {
Box(
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.clickable(onClick = onClose),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = SurferIcons.Close,
// The only content of the button, so it has to carry its name.
contentDescription = stringResource(Res.string.uikit_close),
tint = AppTheme.materialColors.onSurfaceVariant,
modifier = Modifier.size(18.dp),
)
}
}