Pith - kiln
kiln/app/src/main/java/com/vgmlr/kiln/KilnAccountSettings.kt [13.0 kb]
Modified: 18:59:25 154 026 (20 Aug 026)
3 Days Ago
package com.vgmlr.kiln

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.material.icons.filled.Add
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.Create
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

private val PurgeRed = Color(0xFF8B0000)
private val FIELD_TEXT = 17.sp
private val BOOL_OPTIONS = listOf("False", "True")
private val MOVE_BTN = 36.dp
private val MOVE_ICON = 22.dp

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun KilnAccountSettings(vm: KilnViewModel, onBack: () -> Unit, onDeleted: () -> Unit) {
    val account by vm.activeKilnAccount.collectAsState()
    val buttons by vm.buttons.collectAsState()
    val hair = KilnHair.of(account?.hair)
    var confirmDelete by remember { mutableStateOf(false) }
    var creating by remember { mutableStateOf(false) }
    var editing by remember { mutableStateOf<KilnButton?>(null) }

    var pmsMsg by remember(account?.id) { mutableStateOf(account?.pmsMessage.orEmpty()) }
    var mensMsg by remember(account?.id) {
        mutableStateOf(account?.menstruationMessage.orEmpty())
    }

    Scaffold(containerColor = CBg, topBar = {
        TopAppBar(
            title = {
                Text(
                    account?.name.orEmpty(), color = TopBarFg, fontSize = 18.sp,
                    maxLines = 1, overflow = TextOverflow.Ellipsis
                )
            },
            colors = TopAppBarDefaults.topAppBarColors(containerColor = TopBarBg),
            navigationIcon = {
                IconButton(onClick = onBack) {
                    Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = TopBarFg)
                }
            }
        )
    }) { pad ->
        Column(
            Modifier.fillMaxSize().background(CBg)
                .padding(top = pad.calculateTopPadding())
                .verticalScroll(rememberScrollState())
                .padding(horizontal = 16.dp)
        ) {
            Spacer(Modifier.height(20.dp))

            WgSection("Appearance") {
                WgRow("Hair Color") {
                    WgImageDropdown(
                        value = KilnFaces.happy(hair),
                        options = KilnHair.entries.map { KilnFaces.happy(it) }
                    ) { i -> vm.setHair(KilnHair.entries[i]) }
                }
            }

            WgSection("Notifications") {
                WgRow("PMS Alert") {
                    BoolRadio(account?.pmsAlert == true) { vm.setPmsAlert(it) }
                }
                WgDivider()
                WgRow("Message", edit = true) {
                    WgTextField(pmsMsg, placeholder = "none") {
                        pmsMsg = it
                        vm.setPmsMessage(it)
                    }
                }
                WgDivider()
                WgRow("Menstruation Alert") {
                    BoolRadio(account?.menstruationAlert == true) { vm.setMenstruationAlert(it) }
                }
                WgDivider()
                WgRow("Message", edit = true) {
                    WgTextField(mensMsg, placeholder = "none") {
                        mensMsg = it
                        vm.setMenstruationMessage(it)
                    }
                }
                WgDivider()
                WgRow("Menstruation Reminder") {
                    BoolRadio(
                        value = account?.menstruationReminder == true,
                        enabled = account?.menstruationAlert == true
                    ) { vm.setMenstruationReminder(it) }
                }
            }

            WgSection("Symptoms") {
                buttons.forEachIndexed { i, b ->
                    if (i > 0) WgDivider()
                    WgRow(b.label) {
                        MoveButton(Icons.Default.KeyboardArrowUp, "Move Up", i > 0) {
                            vm.moveButton(b, -1)
                        }
                        MoveButton(
                            Icons.Default.KeyboardArrowDown, "Move Down", i < buttons.lastIndex
                        ) { vm.moveButton(b, 1) }
                        IconButton({ editing = b }, Modifier.size(36.dp)) {
                            Icon(Icons.Outlined.Create, "Edit", Modifier.size(18.dp), tint = CText)
                        }
                        IconButton({ vm.deleteButton(b) }, Modifier.size(36.dp)) {
                            Icon(Icons.Outlined.Clear, "Delete", Modifier.size(18.dp), tint = CText)
                        }
                    }
                }
                if (buttons.isNotEmpty()) WgDivider()
                WgRow("New Symptom", bg = CChip) {
                    IconButton({ creating = true }, Modifier.size(36.dp)) {
                        Icon(Icons.Default.Add, "New Symptom", Modifier.size(20.dp), tint = CText)
                    }
                }
            }

            WgSection("Account") {
                WgRow("Delete Account") {
                    Box(
                        Modifier
                            .height(28.dp)
                            .clip(RoundedCornerShape(7.dp))
                            .background(PurgeRed)
                            .border(1.dp, CMuted.copy(alpha = 0.30f), RoundedCornerShape(7.dp))
                            .clickable { confirmDelete = true }
                            .padding(horizontal = 16.dp),
                        contentAlignment = Alignment.Center
                    ) {
                        Text("Purge", color = CText, fontSize = 14.sp)
                    }
                }
            }

            Spacer(Modifier.height(24.dp + pad.calculateBottomPadding()))
        }
    }
    if (confirmDelete) {
        val btnShape = RoundedCornerShape(8.dp)
        val btnColors = ButtonDefaults.buttonColors(
            containerColor = CChip,
            contentColor = CText
        )
        AlertDialog(
            onDismissRequest = { confirmDelete = false },
            shape = RoundedCornerShape(14.dp),
            containerColor = CCard,
            confirmButton = {
                Button(
                    onClick = {
                        confirmDelete = false
                        vm.deleteActiveAccount()
                        onDeleted()
                    },
                    modifier = Modifier.height(44.dp),
                    colors = btnColors,
                    shape = btnShape
                ) { Text("Yep", color = CText, fontSize = 15.sp) }
            },
            dismissButton = {
                Button(
                    onClick = { confirmDelete = false },
                    modifier = Modifier.height(44.dp),
                    colors = btnColors,
                    shape = btnShape
                ) { Text("Nah", color = CText, fontSize = 15.sp) }
            },
            text = {
                Text(
                    "Delete ${account?.name.orEmpty()}?",
                    fontSize = 17.sp
                )
            }
        )
    }
    if (creating) ButtonDialog(
        initial = null,
        onDismiss = { creating = false },
        onConfirm = { label, kind -> vm.addButton(label, kind); creating = false }
    )
    editing?.let { b ->
        ButtonDialog(
            initial = b,
            onDismiss = { editing = null },
            onConfirm = { label, kind -> vm.updateButton(b, label, kind); editing = null }
        )
    }
}

@Composable
private fun MoveButton(icon: ImageVector, desc: String, enabled: Boolean, onClick: () -> Unit) {
    IconButton(onClick, Modifier.size(MOVE_BTN), enabled) {
        Icon(
            icon, desc, Modifier.size(MOVE_ICON),
            tint = CText.copy(alpha = if (enabled) 1f else 0.25f)
        )
    }
}

@Composable
private fun BoolRadio(value: Boolean, enabled: Boolean = true, onSet: (Boolean) -> Unit) {
    WgRadio(
        value = if (value) "True" else "False",
        options = BOOL_OPTIONS,
        disabledOptions = if (enabled) emptyList() else BOOL_OPTIONS
    ) { onSet(it == "True") }
}

@Composable
private fun ButtonDialog(
    initial: KilnButton?,
    onDismiss: () -> Unit,
    onConfirm: (String, KilnButtonKind) -> Unit
) {
    var label by remember { mutableStateOf(initial?.label.orEmpty()) }
    var kind by remember { mutableStateOf(initial?.kindEnum ?: KilnButtonKind.NOT_PERIOD) }
    val btnShape = RoundedCornerShape(8.dp)
    val btnColors = ButtonDefaults.buttonColors(containerColor = CChip, contentColor = CText)

    AlertDialog(
        onDismissRequest = onDismiss,
        shape = RoundedCornerShape(14.dp),
        containerColor = CCard,
        text = {
            Column(verticalArrangement = Arrangement.spacedBy(14.dp)) {
                OutlinedTextField(
                    value = label,
                    onValueChange = { label = it },
                    label = { Text("Label") },
                    singleLine = true,
                    modifier = Modifier.fillMaxWidth(),
                    textStyle = TextStyle(fontSize = FIELD_TEXT),
                    colors = fieldColors()
                )
                SeverityField(kind) { kind = it }
            }
        },
        confirmButton = {
            Button(
                onClick = { onConfirm(label, kind) },
                enabled = label.isNotBlank(),
                modifier = Modifier.height(44.dp),
                colors = btnColors,
                shape = btnShape
            ) {
                Text(
                    "Save",
                    color = CText.copy(alpha = if (label.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) }
        }
    )
}

@Composable
private fun fieldColors() = 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)
)

@Composable
private fun SeverityField(kind: KilnButtonKind, onSelect: (KilnButtonKind) -> Unit) {
    var expanded by remember { mutableStateOf(false) }

    Box(Modifier.fillMaxWidth()) {
        OutlinedTextField(
            value = kind.label,
            onValueChange = { },
            readOnly = true,
            label = { Text("Severity") },
            singleLine = true,
            trailingIcon = {
                Icon(Icons.Default.KeyboardArrowDown, null, Modifier.size(18.dp), tint = CMuted)
            },
            modifier = Modifier.fillMaxWidth(),
            textStyle = TextStyle(fontSize = FIELD_TEXT),
            colors = fieldColors()
        )
        Box(
            Modifier.matchParentSize().clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null
            ) { expanded = true }
        )
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false },
            containerColor = Color(0xFF1F2226),
            shape = RoundedCornerShape(10.dp),
            tonalElevation = 0.dp,
            shadowElevation = 0.dp
        ) {
            KilnButtonKind.entries.forEach { k ->
                DropdownMenuItem(
                    text = {
                        Text(
                            k.label,
                            color = if (k == kind) CMuted else CText,
                            fontSize = FS,
                            maxLines = 1
                        )
                    },
                    onClick = { onSelect(k); expanded = false },
                    contentPadding = PaddingValues(horizontal = 14.dp)
                )
            }
        }
    }
}
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.00243
257,125 (+379)