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)
}
}