package com.vgmlr.kerf
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
internal val CBg = Color(0xFF0E0F11)
internal val CCard = Color(0xFF272A30)
internal val CText = Color(0xFFE8EAED)
internal val CMuted = Color(0xFF868D97)
internal val CChip = Color(0xFF3A4048)
internal val ROW_H = 54.dp
internal val FS = 15.sp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(vm: KerfViewModel, onBack: () -> Unit) {
val kerfColors = LocalKerfColors.current!!
val sanitizeEnabled by vm.sanitizeEnabled.collectAsState()
val extractUrlEnabled by vm.extractUrlEnabled.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = { Text("Settings", fontSize = 18.sp, color = MaterialTheme.colorScheme.onPrimary) },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, null, tint = MaterialTheme.colorScheme.onPrimary)
}
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = kerfColors.appTopbar)
)
},
containerColor = CBg
) { p ->
Column(
Modifier
.padding(p)
.fillMaxSize()
.background(CBg)
.padding(horizontal = KerfDimens.PaddingLarge)
.verticalScroll(rememberScrollState())
) {
Spacer(Modifier.height(20.dp))
KfSection {
KfRow("Sanitize URL") {
KfRadio(
value = if (sanitizeEnabled) "True" else "False",
options = listOf("True", "False")
) { vm.setSanitize(it == "True") }
}
KfDivider()
KfRow("Extract URL") {
KfRadio(
value = if (extractUrlEnabled) "True" else "False",
options = listOf("True", "False")
) { vm.setExtractUrl(it == "True") }
}
}
Spacer(Modifier.height(24.dp))
}
}
}
@Composable
internal fun KfSection(title: String? = null, content: @Composable ColumnScope.() -> Unit) {
if (!title.isNullOrBlank()) {
Text(
text = title.uppercase(),
color = CMuted,
fontSize = 11.sp,
fontWeight = FontWeight.Medium,
letterSpacing = 1.4.sp,
modifier = Modifier.padding(start = 4.dp, bottom = 8.dp)
)
}
KfCard(content)
Spacer(Modifier.height(24.dp))
}
@Composable
internal fun KfCard(content: @Composable ColumnScope.() -> Unit) {
Column(
Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(14.dp))
.background(CCard),
content = content
)
}
@Composable
internal fun KfRow(label: String, content: @Composable RowScope.() -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = ROW_H)
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = label,
color = CText.copy(alpha = 0.7f),
fontSize = FS,
maxLines = 1,
modifier = Modifier.widthIn(min = 92.dp)
)
Spacer(Modifier.width(12.dp))
Row(
modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
content = content
)
}
}
@Composable
internal fun KfDivider() {
HorizontalDivider(thickness = 1.dp, color = CBg)
}
@Composable
internal fun KfValue(
text: String,
modifier: Modifier = Modifier,
mono: Boolean = false,
color: Color = CText,
maxLines: Int = 1
) {
Text(
text = text,
color = color,
fontSize = FS,
fontFamily = if (mono) FontFamily.Monospace else FontFamily.Default,
textAlign = TextAlign.End,
maxLines = maxLines,
overflow = TextOverflow.Ellipsis,
modifier = modifier
)
}
@Composable
private fun KfChip(
label: String,
selected: Boolean,
enabled: Boolean = true,
shape: Shape = RoundedCornerShape(7.dp),
border: Boolean = true,
leftLine: Color? = null,
onClick: () -> Unit
) {
val filled = selected && enabled
Box(
modifier = Modifier
.height(28.dp)
.clip(shape)
.background(if (filled) CChip else Color.Transparent)
.then(
if (leftLine != null) Modifier.drawBehind {
val w = 1.dp.toPx()
drawLine(leftLine, Offset(w / 2f, 0f), Offset(w / 2f, size.height), w)
} else Modifier
)
.then(
if (border) Modifier.border(
width = 1.dp,
color = if (filled) CChip else CMuted.copy(alpha = 0.30f),
shape = shape
) else Modifier
)
.clickable(
enabled = enabled,
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onClick
)
.padding(horizontal = 16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = label,
color = when {
!enabled -> CMuted.copy(alpha = 0.4f)
selected -> CText
else -> CMuted
},
fontSize = 13.sp,
fontFamily = FontFamily.Monospace,
maxLines = 1
)
}
}
@Composable
internal fun KfRadio(
value: String,
options: List<String>,
disabledOptions: List<String> = emptyList(),
onSelect: (String) -> Unit
) {
val shape = RoundedCornerShape(7.dp)
val outline = CMuted.copy(alpha = 0.30f)
Row(
modifier = Modifier
.height(28.dp)
.clip(shape)
.border(1.dp, outline, shape),
verticalAlignment = Alignment.CenterVertically
) {
options.forEachIndexed { i, opt ->
KfChip(
label = opt,
selected = opt == value,
enabled = opt !in disabledOptions,
shape = RectangleShape,
border = false,
leftLine = if (i > 0) outline else null,
onClick = { onSelect(opt) }
)
}
}
}