Add beep tone select, auto dismiss option

This commit is contained in:
Tim Kluge 2024-12-01 10:42:39 +01:00
parent 769737c483
commit eeaf40d98a
4 changed files with 140 additions and 8 deletions

View File

@ -1,6 +1,8 @@
package de.timklge.karooreminder
import android.util.Log
import de.timklge.karooreminder.screens.Reminder
import de.timklge.karooreminder.screens.ReminderBeepPattern
import de.timklge.karooreminder.screens.defaultReminders
import de.timklge.karooreminder.screens.preferencesKey
import io.hammerhead.karooext.KarooSystemService
@ -41,7 +43,14 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", "1.0") {
}
val preferences = applicationContext.dataStore.data.map { remindersJson ->
Json.decodeFromString<MutableList<Reminder>>(remindersJson[preferencesKey] ?: defaultReminders)
try {
Json.decodeFromString<MutableList<Reminder>>(
remindersJson[preferencesKey] ?: defaultReminders
)
} catch(e: Throwable){
Log.e("karoo-reminder","Failed to read preferences", e)
mutableListOf()
}
}
karooSystem.streamDataFlow(DataType.Type.ELAPSED_TIME)
@ -50,19 +59,20 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", "1.0") {
.distinctUntilChanged()
.drop(1)
.combine(preferences) { elapsedMinutes, reminders -> elapsedMinutes to reminders}
.distinctUntilChanged { old, new -> old.first == new.first }
.collect { (elapsedMinutes, reminders) ->
reminders
.filter { reminder -> reminder.isActive && elapsedMinutes % reminder.interval == 0 }
.forEach { reminder ->
karooSystem.dispatch(TurnScreenOn)
delay(1_000)
karooSystem.dispatch(PlayBeepPattern(listOf(PlayBeepPattern.Tone(300, 200), PlayBeepPattern.Tone(500, 200), PlayBeepPattern.Tone(700, 200))))
if (reminder.tone != ReminderBeepPattern.NO_TONES) karooSystem.dispatch(PlayBeepPattern(reminder.tone.tones))
karooSystem.dispatch(
InRideAlert(
id = "reminder-${reminder.id}-${elapsedMinutes}",
detail = reminder.text,
title = reminder.name,
autoDismissMs = 15_000,
autoDismissMs = if(reminder.isAutoDismiss) 15_000 else null,
icon = R.drawable.ic_launcher,
textColor = R.color.white,
backgroundColor = reminder.getResourceColor(applicationContext)

View File

@ -1,6 +1,7 @@
package de.timklge.karooreminder.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -12,28 +13,34 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
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.filled.Build
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Done
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -42,6 +49,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.core.content.ContextCompat
import com.maxkeppeker.sheets.core.models.base.UseCaseState
import com.maxkeppeler.sheets.color.ColorDialog
@ -51,19 +59,31 @@ import com.maxkeppeler.sheets.color.models.ColorSelectionMode
import com.maxkeppeler.sheets.color.models.MultipleColors
import com.maxkeppeler.sheets.color.models.SingleColor
import de.timklge.karooreminder.R
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.models.PlayBeepPattern
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedReminder: Reminder?) -> Unit, onCancel: () -> Unit){
val ctx = LocalContext.current
val karooSystem = remember { KarooSystemService(ctx) }
LaunchedEffect(Unit) {
karooSystem.connect{}
}
var title by remember { mutableStateOf(reminder.name) }
var text by remember { mutableStateOf(reminder.text) }
var selectedColor by remember { mutableIntStateOf(reminder.foregroundColor) }
val colorDialogState by remember { mutableStateOf(UseCaseState()) }
var duration by remember { mutableStateOf(reminder.interval.toString()) }
var isActive by remember { mutableStateOf(reminder.isActive) }
var autoDismiss by remember { mutableStateOf(reminder.isAutoDismiss) }
var deleteDialogVisible by remember { mutableStateOf(false) }
var toneDialogVisible by remember { mutableStateOf(false) }
var selectedTone by remember { mutableStateOf(reminder.tone) }
fun getUpdatedReminder(): Reminder = Reminder(reminder.id, title, duration.toIntOrNull() ?: 0, text, selectedColor, isActive)
fun getUpdatedReminder(): Reminder = Reminder(reminder.id, title, duration.toIntOrNull() ?: 0,
text, selectedColor, isActive, isAutoDismiss = autoDismiss, tone = selectedTone)
Column(modifier = Modifier
.fillMaxSize()
@ -116,11 +136,28 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
.shadow(5.dp, CircleShape)
.width(40.dp), content = {})
Spacer(modifier = Modifier.width(20.dp))
Spacer(modifier = Modifier.width(5.dp))
Text("Change Color")
}
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(60.dp),
onClick = {
toneDialogVisible = true
}) {
Icon(Icons.Default.Build, contentDescription = "Change Tone")
Spacer(modifier = Modifier.width(5.dp))
Text("Change Tone")
}
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = autoDismiss, onCheckedChange = { autoDismiss = it})
Spacer(modifier = Modifier.width(10.dp))
Text("Auto-Dismiss")
}
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = isActive, onCheckedChange = { isActive = it})
Spacer(modifier = Modifier.width(10.dp))
@ -133,6 +170,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
onSubmit(getUpdatedReminder())
}) {
Icon(Icons.Default.Done, contentDescription = "Save Reminder")
Spacer(modifier = Modifier.width(5.dp))
Text("Save")
}
@ -142,6 +180,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
onCancel()
}) {
Icon(Icons.Default.Close, contentDescription = "Cancel Editing")
Spacer(modifier = Modifier.width(5.dp))
Text("Cancel")
}
@ -169,6 +208,64 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
},
title = { Text("Delete reminder") }, text = { Text("Really delete this reminder?") })
}
if (toneDialogVisible){
Dialog(onDismissRequest = { toneDialogVisible = false }) {
var dialogSelectedTone by remember { mutableStateOf(selectedTone) }
val coroutineScope = rememberCoroutineScope()
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier
.padding(5.dp)
.verticalScroll(rememberScrollState())
.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) {
ReminderBeepPattern.entries.forEach { pattern ->
Row(modifier = Modifier
.fillMaxWidth()
.clickable {
dialogSelectedTone = pattern
karooSystem.dispatch(PlayBeepPattern(pattern.tones))
}, verticalAlignment = Alignment.CenterVertically) {
RadioButton(selected = dialogSelectedTone == pattern, onClick = {
dialogSelectedTone = pattern
karooSystem.dispatch(PlayBeepPattern(pattern.tones))
})
Text(
text = pattern.displayName,
modifier = Modifier.padding(start = 10.dp)
)
}
}
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(50.dp), onClick = {
selectedTone = dialogSelectedTone
toneDialogVisible = false
}) {
Icon(Icons.Default.Done, contentDescription = "Save")
Text("Save")
}
FilledTonalButton(modifier = Modifier
.fillMaxWidth()
.height(50.dp), onClick = {
toneDialogVisible = false
}) {
Icon(Icons.Default.Close, contentDescription = "Cancel")
Text("Cancel")
}
}
}
}
}
}
}
}

View File

@ -1,6 +1,7 @@
package de.timklge.karooreminder.screens
import android.content.Context
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
@ -86,9 +87,15 @@ fun ReminderAppNavHost(modifier: Modifier = Modifier, navController: NavHostCont
val ctx = LocalContext.current
LaunchedEffect(Unit) {
ctx.dataStore.data.distinctUntilChanged().collect { t ->
val entries = Json.decodeFromString<MutableList<Reminder>>(t[preferencesKey] ?: defaultReminders)
reminders.clear()
reminders.addAll(entries)
try {
val entries = Json.decodeFromString<MutableList<Reminder>>(
t[preferencesKey] ?: defaultReminders
)
reminders.addAll(entries)
} catch(e: Throwable){
Log.e("karoo-reminder","Failed to read preferences", e)
}
}
}

View File

@ -3,14 +3,32 @@ package de.timklge.karooreminder.screens
import android.content.Context
import androidx.core.content.ContextCompat
import de.timklge.karooreminder.R
import io.hammerhead.karooext.models.PlayBeepPattern
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
enum class ReminderBeepPattern(val displayName: String, val tones: List<PlayBeepPattern.Tone>) {
NO_TONES("No tones", emptyList()),
THREE_TONES_UP("Three tones up", listOf(PlayBeepPattern.Tone(500, 200), PlayBeepPattern.Tone(700, 200), PlayBeepPattern.Tone(900, 200))),
THREE_TONES_DOWN("Three tones down", listOf(PlayBeepPattern.Tone(900, 200), PlayBeepPattern.Tone(700, 200), PlayBeepPattern.Tone(400, 200))),
DOUBLE_HIGH("Double high", listOf(
PlayBeepPattern.Tone(400, 400),
PlayBeepPattern.Tone(0, 200),
PlayBeepPattern.Tone(600, 200),
PlayBeepPattern.Tone(0, 200),
PlayBeepPattern.Tone(600, 200),
PlayBeepPattern.Tone(0, 200),
PlayBeepPattern.Tone(400, 400))
)
}
@Serializable
class Reminder(val id: Int, var name: String, var interval: Int, var text: String,
var foregroundColor: Int = android.graphics.Color.parseColor("#700000"),
val isActive: Boolean = true){
val isActive: Boolean = true, val isAutoDismiss: Boolean = true,
val tone: ReminderBeepPattern = ReminderBeepPattern.THREE_TONES_UP){
fun getResourceColor(context: Context): Int {
return when(foregroundColor){