package com.vgmlr.kiln
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.WaterDrop
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.TextStyle
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
private val ROW_PAD_H = 18.dp
private val ROW_PAD_V = 14.dp
private val FACE_SIZE = 44.dp
private val FACE_GAP = 14.dp
private val DividerColor = Color.White.copy(alpha = 0.08f)
private val FIELD_TEXT = 17.sp
private const val EMPTY_TEXT =
"\"..ayh don't trust any thing\nthat bleeds for five days\nand doesn't die.\""
@Composable
fun HomeScreen(
summaries: List<AccountSummary>,
onOpen: (KilnAccount) -> Unit,
onAdd: (String) -> Unit,
onSettings: () -> Unit,
onImport: (Uri, (Boolean) -> Unit) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
var showVersion by remember { mutableStateOf(false) }
var importFailed by remember { mutableStateOf(false) }
val uriHandler = LocalUriHandler.current
val importer = rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri -> uri?.let { u -> onImport(u) { ok -> importFailed = !ok } } }
Scaffold(
topBar = {
KilnTopBar(
"kiln",
onSettings = onSettings,
onVersion = { showVersion = true },
onImport = { importer.launch(arrayOf("*/*")) },
action = {
IconButton(onClick = { showDialog = true }) {
Icon(Icons.Default.Add, "New Account", tint = TopBarFg)
}
}
)
}
) { pad ->
Box(Modifier.fillMaxSize().padding(top = pad.calculateTopPadding())) {
if (summaries.isEmpty()) {
Text(
EMPTY_TEXT,
Modifier.align(Alignment.Center).padding(horizontal = ROW_PAD_H),
fontSize = 14.sp,
lineHeight = 22.sp,
textAlign = TextAlign.Center,
color = Color.White.copy(alpha = 0.4f)
)
} else {
LazyColumn(
Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = pad.calculateBottomPadding())
) {
itemsIndexed(summaries, key = { _, s -> s.kilnAccount.id }) { i, s ->
if (i > 0) HorizontalDivider(color = DividerColor)
AccountRow(s) { onOpen(s.kilnAccount) }
}
}
}
}
}
if (showDialog) NewAccountDialog(
onDismiss = { showDialog = false },
onConfirm = { onAdd(it); showDialog = false }
)
VersionDialog(showVersion, onDismiss = { showVersion = false }, uriHandler = uriHandler)
if (importFailed) AlertDialog(
onDismissRequest = { importFailed = false },
shape = RoundedCornerShape(14.dp),
containerColor = CCard,
confirmButton = {
Button(
onClick = { importFailed = false },
modifier = Modifier.height(44.dp),
colors = ButtonDefaults.buttonColors(
containerColor = CChip, contentColor = CText
),
shape = RoundedCornerShape(8.dp)
) { Text("Fine", color = CText, fontSize = 15.sp) }
},
text = { Text("Not a kiln_[timestamp].json file.", fontSize = 17.sp) }
)
}
@Composable
private fun AccountRow(summary: AccountSummary, onClick: () -> Unit) {
Row(
Modifier.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = ROW_PAD_H, vertical = ROW_PAD_V),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painterResource(KilnFaces.happy(KilnHair.of(summary.kilnAccount.hair))),
null,
Modifier.size(FACE_SIZE)
)
Spacer(Modifier.width(FACE_GAP))
Column(Modifier.weight(1f)) {
Text(
summary.kilnAccount.name,
fontSize = 17.sp,
fontWeight = FontWeight.SemiBold,
letterSpacing = 0.2.sp,
color = Color.White.copy(alpha = 0.92f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
"Next Pre-Menstruation: ${daysLabel(summary.daysToPreMenstrual)}",
fontSize = 12.sp,
color = Color.White.copy(alpha = 0.55f)
)
}
if (summary.isDefault) {
Icon(
Icons.Filled.Favorite,
"Default Account",
Modifier.size(FACE_SIZE / 2),
tint = colorResource(ICON_BG[KilnGradientPalette.bucket(summary.todayHue)])
)
Spacer(Modifier.width(FACE_GAP))
}
Icon(
Icons.Filled.WaterDrop,
null,
Modifier.size(FACE_SIZE / 2),
tint = KilnGradientPalette.colorFor(summary.todayHue)
)
}
}
private fun daysLabel(days: Long?): String = when (days) {
null -> "—"
0L -> "Today"
1L -> "1 Day"
else -> "$days Days"
}
private val ICON_BG = intArrayOf(
R.color.icon_bg_g0, R.color.icon_bg_g1, R.color.icon_bg_g2, R.color.icon_bg_g3,
R.color.icon_bg_g4, R.color.icon_bg_g5, R.color.icon_bg_g6, R.color.icon_bg_g7
)
@Composable
private fun NewAccountDialog(onDismiss: () -> Unit, onConfirm: (String) -> Unit) {
var name by remember { mutableStateOf("") }
val btnShape = RoundedCornerShape(8.dp)
val btnColors = ButtonDefaults.buttonColors(
containerColor = CChip,
contentColor = CText
)
AlertDialog(
onDismissRequest = onDismiss,
shape = RoundedCornerShape(14.dp),
containerColor = CCard,
text = {
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
singleLine = true,
textStyle = TextStyle(fontSize = FIELD_TEXT),
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = CText,
unfocusedTextColor = CText,
focusedBorderColor = Color.White.copy(alpha = 0.45f),
unfocusedBorderColor = Color.White.copy(alpha = 0.15f),
focusedLabelColor = Color.White.copy(alpha = 0.60f),
unfocusedLabelColor = Color.White.copy(alpha = 0.40f),
cursorColor = Color.White.copy(alpha = 0.80f)
)
)
},
confirmButton = {
Button(
onClick = { onConfirm(name) },
enabled = name.isNotBlank(),
modifier = Modifier.height(44.dp),
colors = btnColors,
shape = btnShape
) {
Text(
"Create",
color = CText.copy(alpha = if (name.isNotBlank()) 1f else 0.3f),
fontSize = 15.sp
)
}
},
dismissButton = {
Button(
onClick = onDismiss,
modifier = Modifier.height(44.dp),
colors = btnColors,
shape = btnShape
) { Text("Cancel", color = CText, fontSize = 15.sp) }
}
)
}