Pith - wedge_android
wedge_android/app/src/main/java/com/vgmlr/wedge/WedgeSettings.kt [27.3 kb]
Modified: 06:16:54 91 026 (18 Jun 026)
6 Days Ago
package com.vgmlr.wedge

import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
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.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import kotlinx.coroutines.delay

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsView(vm: MainViewModel, prefs: PreferenceManager, onBack: () -> Unit) {
    val context = LocalContext.current
    val editorBg by prefs.bgColor.collectAsState(WedgeConfig.BG_COLOR_DEFAULT)
    val editorTxt by prefs.textColor.collectAsState(WedgeConfig.TEXT_COLOR_DEFAULT)

    val aTxt by prefs.textColor.collectAsState(WedgeConfig.TEXT_COLOR_DEFAULT)
    val aBg by prefs.bgColor.collectAsState(WedgeConfig.BG_COLOR_DEFAULT)
    val aWTxt by prefs.widgetTextColor.collectAsState(WedgeConfig.WIDGET_TEXT_COLOR_DEFAULT)
    val aWBg by prefs.widgetBgColor.collectAsState(WedgeConfig.WIDGET_BG_COLOR_DEFAULT)
    val aESize by prefs.editorFontSize.collectAsState(WedgeConfig.FONT_SIZE_DEFAULT)
    val aWSize by prefs.widgetFontSize.collectAsState(WedgeConfig.WIDGET_FONT_SIZE_DEFAULT)
    val aLHeight by prefs.lineHeight.collectAsState(WedgeConfig.LINE_HEIGHT_DEFAULT)
    val aBColor by prefs.boldColor.collectAsState(WedgeConfig.BOLD_COLOR_DEFAULT)
    val aWBCol by prefs.widgetBoldColor.collectAsState(WedgeConfig.WIDGET_BOLD_COLOR_DEFAULT)
    val aNextBg by prefs.nextTimeBg.collectAsState(WedgeConfig.CALENDAR_COLOR_DEFAULT)
    val aWNextBg by prefs.widgetNextTimeBg.collectAsState(WedgeConfig.WIDGET_CALENDAR_COLOR_DEFAULT)
    val aIncognito by prefs.incognitoMode.collectAsState(false)
    val aAlarmUri by prefs.alarmSoundUri.collectAsState("")
    val aAlarmDuration by prefs.alarmDurationMinutes.collectAsState(5)
    val aVibration by prefs.vibrationEnabled.collectAsState(false)

    var tTxt by remember(aTxt) { mutableStateOf(aTxt) }
    var tBg by remember(aBg) { mutableStateOf(aBg) }
    var tWTxt by remember(aWTxt) { mutableStateOf(aWTxt) }
    var tWBg by remember(aWBg) { mutableStateOf(aWBg) }
    var tBCol by remember(aBColor) { mutableStateOf(aBColor) }
    var tWBCol by remember(aWBCol) { mutableStateOf(aWBCol) }
    var tNextBg by remember(aNextBg) { mutableStateOf(aNextBg) }
    var tWNextBg by remember(aWNextBg) { mutableStateOf(aWNextBg) }
    var tIncognito by remember(aIncognito) { mutableStateOf(aIncognito) }
    var tAlarmUri by remember(aAlarmUri) { mutableStateOf(aAlarmUri) }
    var tAlarmDuration by remember(aAlarmDuration) { mutableIntStateOf(aAlarmDuration) }
    var tVibration by remember(aVibration) { mutableStateOf(aVibration) }
    var dropdownExpanded by remember { mutableStateOf(false) }
    var vibDropdownExpanded by remember { mutableStateOf(false) }

    var tESize by remember(aESize) { mutableStateOf(aESize.toString()) }
    var tWSize by remember(aWSize) { mutableStateOf(aWSize.toString()) }
    var tLHeight by remember(aLHeight) { mutableStateOf(aLHeight.toString()) }

    LaunchedEffect(tTxt, tBg, tWTxt, tWBg, tBCol, tWBCol, tNextBg, tWNextBg, tESize, tWSize, tLHeight) {
        val finalE = tESize.toFloatOrNull()?.coerceIn(WedgeConfig.FONT_SIZE_MIN, WedgeConfig.FONT_SIZE_MAX) ?: WedgeConfig.FONT_SIZE_DEFAULT
        val finalW = tWSize.toFloatOrNull()?.coerceIn(WedgeConfig.FONT_SIZE_MIN, WedgeConfig.FONT_SIZE_MAX) ?: WedgeConfig.WIDGET_FONT_SIZE_DEFAULT
        val finalLH = tLHeight.toFloatOrNull() ?: WedgeConfig.LINE_HEIGHT_DEFAULT

        val hasColorChanges = tTxt != aTxt || tBg != aBg || tWTxt != aWTxt || tWBg != aWBg ||
                tBCol != aBColor || tWBCol != aWBCol || tNextBg != aNextBg || tWNextBg != aWNextBg
        val hasFontChanges = finalE != aESize || finalW != aWSize || finalLH != aLHeight

        if (hasColorChanges || hasFontChanges) {
            delay(500)
            if (hasColorChanges) {
                prefs.setColors(tTxt, tBg, tWTxt, tWBg, tBCol, tWBCol, tNextBg, tWNextBg)
            }
            if (hasFontChanges) {
                prefs.setFontSettings(finalE, finalW, finalLH)
            }
            NoteWidgetProvider.triggerUpdate(context)
        }
    }

    LaunchedEffect(tIncognito) {
        if (tIncognito != aIncognito) {
            prefs.setIncognito(tIncognito)
            NoteWidgetProvider.triggerUpdate(context)
        }
    }

    LaunchedEffect(tAlarmUri, tAlarmDuration, tVibration) {
        if (tAlarmUri != aAlarmUri || tAlarmDuration != aAlarmDuration || tVibration != aVibration) {
            prefs.setAlarmSettings(tAlarmUri, tAlarmDuration, tVibration)
            NoteWidgetProvider.triggerUpdate(context)
        }
    }

    val audioPickerLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.GetContent()
    ) { uri: Uri? ->
        uri?.let { pickedUri ->
            try {
                var displayName = "alarm_sound"
                if (pickedUri.scheme == "content") {
                    context.contentResolver.query(pickedUri, null, null, null, null)?.use { cursor ->
                        val nameIdx = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
                        if (nameIdx != -1 && cursor.moveToFirst()) {
                            displayName = cursor.getString(nameIdx)
                        }
                    }
                } else {
                    pickedUri.path?.substringAfterLast('/')?.let { displayName = it }
                }

                context.filesDir.listFiles()?.forEach { file ->
                    if (file.name.startsWith("custom_alarm_")) {
                        file.delete()
                    }
                }

                val localFile = java.io.File(context.filesDir, "custom_alarm_$displayName")
                context.contentResolver.openInputStream(pickedUri)?.use { input ->
                    localFile.outputStream().use { output ->
                        input.copyTo(output)
                    }
                }

                tAlarmUri = Uri.fromFile(localFile).toString()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Settings", color = colorResource(id = R.color.title_color), fontSize = 18.sp) },
                colors = TopAppBarDefaults.topAppBarColors(containerColor = Color(0xFF486860)),
                navigationIcon = {
                    IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null, tint = colorResource(id = R.color.title_color)) }
                }
            )
        }
    ) { p ->
        Column(Modifier.padding(p).fillMaxSize().imePadding().background(parseColor(editorBg)).padding(horizontal = 16.dp).verticalScroll(rememberScrollState())) {

            Column(modifier = Modifier.fillMaxWidth()) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(top = 20.dp, bottom = 0.dp)
                        .background(colorResource(id = R.color.calc_bg_color)),
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.Center
                ) {
                    Text(
                        text = "Sync",
                        modifier = Modifier.padding(vertical = 7.dp),
                        color = colorResource(id = R.color.data_color),
                        style = MaterialTheme.typography.bodyMedium.copy(
                            letterSpacing = 1.5.sp
                        )
                    )
                }
            }
            
            SyncViewContent(vm, prefs)

            val labelCol = parseColor(editorTxt).copy(alpha = 0.6f)
            val fieldCol = OutlinedTextFieldDefaults.colors(
                focusedTextColor = parseColor(editorTxt),
                unfocusedTextColor = parseColor(editorTxt),
                focusedBorderColor = parseColor(editorTxt).copy(alpha = 0.5f),
                unfocusedBorderColor = parseColor(editorTxt).copy(alpha = 0.2f)
            )

            Column(modifier = Modifier.fillMaxWidth()) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(top = 25.dp, bottom = 20.dp)
                        .background(colorResource(id = R.color.calc_bg_color)),
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.Center
                ) {
                    Text(
                        text = "Alarm",
                        modifier = Modifier.padding(vertical = 7.dp),
                        color = colorResource(id = R.color.data_color),
                        style = MaterialTheme.typography.bodyMedium.copy(
                            letterSpacing = 1.5.sp
                        )
                    )
                }
            }
            
            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Alarm Sound", labelCol)
                }
                    val alarmDisplayName = remember(tAlarmUri) {
                        if (tAlarmUri.isEmpty()) {
                            ""
                        } else {
                            val uri = tAlarmUri.toUri()
                            var resolvedName = ""
                            if (uri.scheme == "content") {
                                try {
                                    context.contentResolver.query(uri, null, null, null, null)
                                        ?.use { cursor ->
                                            val nameIndex =
                                                cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
                                            if (nameIndex != -1 && cursor.moveToFirst()) {
                                                resolvedName = cursor.getString(nameIndex)
                                            }
                                        }
                                } catch (_: Exception) {
                                }
                            }
                            val baseName = resolvedName.ifEmpty {
                                uri.path?.substringAfterLast('/') ?: tAlarmUri
                            }

                            if (baseName.startsWith("custom_alarm_")) {
                                baseName.removePrefix("custom_alarm_")
                            } else {
                                baseName
                            }
                        }
                    }
                val truncatedName = if (alarmDisplayName.length > 25) alarmDisplayName.take(22) + "..." else alarmDisplayName
                Box(
                    modifier = Modifier
                        .weight(1f)
                        .clickable { audioPickerLauncher.launch("audio/*") }
                ) {
                    OutlinedTextField(
                        value = truncatedName,
                        onValueChange = {},
                        readOnly = true,
                        enabled = false, 
                        placeholder = { Text("default.wav", color = labelCol.copy(alpha = 0.5f)) },
                        modifier = Modifier.fillMaxWidth(),
                        colors = OutlinedTextFieldDefaults.colors(
                            disabledTextColor = parseColor(editorTxt).copy(alpha = 1.0f),
                            disabledPlaceholderColor = parseColor(editorTxt).copy(alpha = 0.5f),
                            disabledBorderColor = parseColor(editorTxt).copy(alpha = 0.2f)
                        )
                    )
                }
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Alarm Duration", labelCol)
                }
                ExposedDropdownMenuBox(
                    expanded = dropdownExpanded,
                    onExpandedChange = { dropdownExpanded = !dropdownExpanded }
                ) {
                    OutlinedTextField(
                        value = tAlarmDuration.toString(),
                        onValueChange = {},
                        readOnly = true,
                        trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = dropdownExpanded) },
                        modifier = Modifier.fillMaxWidth()
                            .menuAnchor(MenuAnchorType.PrimaryEditable, true),
                        colors = fieldCol
                    )
                    ExposedDropdownMenu(
                        expanded = dropdownExpanded,
                        onDismissRequest = { dropdownExpanded = false }
                    ) {
                        (1..10).forEach { minute ->
                            DropdownMenuItem(
                                text = { Text(text = minute.toString()) },
                                onClick = {
                                    tAlarmDuration = minute
                                    dropdownExpanded = false
                                }
                            )
                        }
                    }
                }
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Vibration", labelCol)
                }
                ExposedDropdownMenuBox(
                    expanded = vibDropdownExpanded,
                    onExpandedChange = { vibDropdownExpanded = !vibDropdownExpanded }
                ) {
                    OutlinedTextField(
                        value = if (tVibration) "True" else "False",
                        onValueChange = {},
                        readOnly = true,
                        trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = vibDropdownExpanded) },
                        modifier = Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryEditable, true),
                        colors = fieldCol
                    )
                    ExposedDropdownMenu(
                        expanded = vibDropdownExpanded,
                        onDismissRequest = { vibDropdownExpanded = false }
                    ) {
                        DropdownMenuItem(
                            text = { Text(text = "True") },
                            onClick = {
                                tVibration = true
                                vibDropdownExpanded = false
                            }
                        )
                        DropdownMenuItem(
                            text = { Text(text = "False") },
                            onClick = {
                                tVibration = false
                                vibDropdownExpanded = false
                            }
                        )
                    }
                }
            }

            Column(modifier = Modifier.fillMaxWidth()) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(top = 20.dp, bottom = 20.dp)
                        .background(colorResource(id = R.color.calc_bg_color)),
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.Center
                ) {
                    Text(
                        text = "Editor",
                        modifier = Modifier.padding(vertical = 7.dp),
                        color = colorResource(id = R.color.data_color),
                        style = MaterialTheme.typography.bodyMedium.copy(
                            letterSpacing = 1.5.sp
                        )
                    )
                }
            }

            val bCol = parseColor(editorTxt).copy(alpha = 0.2f)
            
            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Text Color", labelCol)
                }
                SetField(
                    tTxt,
                    { tTxt = it },
                    WedgeConfig.TEXT_COLOR_DEFAULT,
                    fieldCol,
                    parseColor(tTxt),
                    bCol
                )
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Focus Color", labelCol)
                }
                SetField(
                    tBCol,
                    { tBCol = it },
                    WedgeConfig.BOLD_COLOR_DEFAULT,
                    fieldCol,
                    parseColor(tBCol),
                    bCol
                )
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Next Color", labelCol)
                }
                SetField(
                    tNextBg,
                    { tNextBg = it },
                    WedgeConfig.CALENDAR_COLOR_DEFAULT,
                    fieldCol,
                    parseColor(tNextBg),
                    bCol
                )
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Ground Color", labelCol)
                }
                SetField(
                    tBg,
                    { tBg = it },
                    WedgeConfig.BG_COLOR_DEFAULT,
                    fieldCol,
                    parseColor(tBg),
                    bCol
                )
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Font Size", labelCol)
                }
                OutlinedTextField(
                    value = tESize,
                    onValueChange = {
                        if (it.all { char -> char.isDigit() || char == '.' }) tESize = it
                    },
                    modifier = Modifier.fillMaxWidth(),
                    colors = fieldCol,
                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                )
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Line Height", labelCol)
                }
                OutlinedTextField(
                    value = tLHeight,
                    onValueChange = {
                        if (it.all { char -> char.isDigit() || char == '.' }) tLHeight = it
                    },
                    modifier = Modifier.fillMaxWidth(),
                    colors = fieldCol,
                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal)
                )
            }

            Spacer(Modifier.height(8.dp))

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Incognito Mode", labelCol)
                }
                Switch(
                    checked = tIncognito,
                    onCheckedChange = { tIncognito = it },
                    colors = SwitchDefaults.colors(
                        checkedThumbColor = Color.Gray,
                        checkedTrackColor = Color.Transparent,
                        checkedBorderColor = Color.Gray,
                        uncheckedThumbColor = Color.Gray,
                        uncheckedTrackColor = Color.Transparent,
                        uncheckedBorderColor = Color.Gray.copy(alpha = 0.5f)
                    )
                )
            }

            Column(modifier = Modifier.fillMaxWidth()) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(top = 10.dp, bottom = 20.dp)
                        .background(colorResource(id = R.color.calc_bg_color)),
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.Center
                ) {
                    Text(
                        text = "Widget",
                        modifier = Modifier.padding(vertical = 7.dp),
                        color = colorResource(id = R.color.data_color),
                        style = MaterialTheme.typography.bodyMedium.copy(
                            letterSpacing = 1.5.sp
                        )
                    )
                }
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Text Color", labelCol)
                }
                SetField(
                    tWTxt,
                    { tWTxt = it },
                    WedgeConfig.WIDGET_TEXT_COLOR_DEFAULT,
                    fieldCol,
                    parseColor(tWTxt),
                    bCol
                )
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Focus Color", labelCol)
                }
                SetField(
                    tWBCol,
                    { tWBCol = it },
                    WedgeConfig.WIDGET_BOLD_COLOR_DEFAULT,
                    fieldCol,
                    parseColor(tWBCol),
                    bCol
                )
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Next Color", labelCol)
                }
                SetField(
                    tWNextBg,
                    { tWNextBg = it },
                    WedgeConfig.WIDGET_CALENDAR_COLOR_DEFAULT,
                    fieldCol,
                    parseColor(tWNextBg),
                    bCol
                )
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Ground Color", labelCol)
                }
                SetField(
                    tWBg,
                    { tWBg = it },
                    WedgeConfig.WIDGET_BG_COLOR_DEFAULT,
                    fieldCol,
                    parseColor(tWBg),
                    bCol
                )
            }

            Row(
                modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Start
            ) {
                Box(modifier = Modifier.width(140.dp)) {
                    SetLabel("Font Size", labelCol)
                }
                OutlinedTextField(
                    value = tWSize,
                    onValueChange = {
                        if (it.all { char -> char.isDigit() || char == '.' }) tWSize = it
                    },
                    modifier = Modifier.fillMaxWidth(),
                    colors = fieldCol,
                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                )
            }
            Spacer(Modifier.height(16.dp))
        }
    }
}

@Composable
fun SetLabel(text: String, color: Color) {
    Text(
        text,
        color = color,
        fontSize = 16.sp,
        fontWeight = FontWeight.Bold
    )
}

@Composable
fun SetField(value: String, onValueChange: (String) -> Unit, placeholder: String, colors: TextFieldColors, preview: Color? = null, bCol: Color = Color.Transparent) {
    Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
        preview?.let {
            Box(Modifier.size(56.dp)
                .background(it, RoundedCornerShape(4.dp))
                .border(1.dp, bCol, RoundedCornerShape(4.dp))
            )
            Spacer(Modifier.width(12.dp))
        }
        OutlinedTextField(value = value, onValueChange = onValueChange, placeholder = { Text(placeholder) }, modifier = Modifier.weight(1f), singleLine = true, colors = colors)
    }
}
Updates
OTC Applet - Linux 93.026.1
Wedge - Linux 90.026.1
Wedge - Android 90.026.1
Shim - Android 86.026.1
Kerf - Android 86.026.4
Dev
TVShow (227) 'CSA'
TVShow (228) 'APT'
TVProgram (83) 'BXT'
Miter Update(s)
Peen (Messaging)

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)