Pith - kiln
kiln/app/src/main/java/com/vgmlr/kiln/KilnHomeScreen.kt [8.3 kb]
Modified: 18:59:25 154 026 (20 Aug 026)
3 Days Ago
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) }
        }
    )
}
Updates
Kerf - Android 157.026
Kiln - Android 157.026
Wedge - Android 156.026
Whittle - Linux 155.026
Grit - Firefox/Chrome 154.026

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)
0.00231
256,900 (+154)