Add beep tone select, auto dismiss option
This commit is contained in:
parent
769737c483
commit
eeaf40d98a
@ -1,6 +1,8 @@
|
|||||||
package de.timklge.karooreminder
|
package de.timklge.karooreminder
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import de.timklge.karooreminder.screens.Reminder
|
import de.timklge.karooreminder.screens.Reminder
|
||||||
|
import de.timklge.karooreminder.screens.ReminderBeepPattern
|
||||||
import de.timklge.karooreminder.screens.defaultReminders
|
import de.timklge.karooreminder.screens.defaultReminders
|
||||||
import de.timklge.karooreminder.screens.preferencesKey
|
import de.timklge.karooreminder.screens.preferencesKey
|
||||||
import io.hammerhead.karooext.KarooSystemService
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
@ -41,7 +43,14 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", "1.0") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val preferences = applicationContext.dataStore.data.map { remindersJson ->
|
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)
|
karooSystem.streamDataFlow(DataType.Type.ELAPSED_TIME)
|
||||||
@ -50,19 +59,20 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", "1.0") {
|
|||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.drop(1)
|
.drop(1)
|
||||||
.combine(preferences) { elapsedMinutes, reminders -> elapsedMinutes to reminders}
|
.combine(preferences) { elapsedMinutes, reminders -> elapsedMinutes to reminders}
|
||||||
|
.distinctUntilChanged { old, new -> old.first == new.first }
|
||||||
.collect { (elapsedMinutes, reminders) ->
|
.collect { (elapsedMinutes, reminders) ->
|
||||||
reminders
|
reminders
|
||||||
.filter { reminder -> reminder.isActive && elapsedMinutes % reminder.interval == 0 }
|
.filter { reminder -> reminder.isActive && elapsedMinutes % reminder.interval == 0 }
|
||||||
.forEach { reminder ->
|
.forEach { reminder ->
|
||||||
karooSystem.dispatch(TurnScreenOn)
|
karooSystem.dispatch(TurnScreenOn)
|
||||||
delay(1_000)
|
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(
|
karooSystem.dispatch(
|
||||||
InRideAlert(
|
InRideAlert(
|
||||||
id = "reminder-${reminder.id}-${elapsedMinutes}",
|
id = "reminder-${reminder.id}-${elapsedMinutes}",
|
||||||
detail = reminder.text,
|
detail = reminder.text,
|
||||||
title = reminder.name,
|
title = reminder.name,
|
||||||
autoDismissMs = 15_000,
|
autoDismissMs = if(reminder.isAutoDismiss) 15_000 else null,
|
||||||
icon = R.drawable.ic_launcher,
|
icon = R.drawable.ic_launcher,
|
||||||
textColor = R.color.white,
|
textColor = R.color.white,
|
||||||
backgroundColor = reminder.getResourceColor(applicationContext)
|
backgroundColor = reminder.getResourceColor(applicationContext)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package de.timklge.karooreminder.screens
|
package de.timklge.karooreminder.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
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.Close
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.filled.Done
|
import androidx.compose.material.icons.filled.Done
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.maxkeppeker.sheets.core.models.base.UseCaseState
|
import com.maxkeppeker.sheets.core.models.base.UseCaseState
|
||||||
import com.maxkeppeler.sheets.color.ColorDialog
|
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.MultipleColors
|
||||||
import com.maxkeppeler.sheets.color.models.SingleColor
|
import com.maxkeppeler.sheets.color.models.SingleColor
|
||||||
import de.timklge.karooreminder.R
|
import de.timklge.karooreminder.R
|
||||||
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
|
import io.hammerhead.karooext.models.PlayBeepPattern
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedReminder: Reminder?) -> Unit, onCancel: () -> Unit){
|
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 title by remember { mutableStateOf(reminder.name) }
|
||||||
var text by remember { mutableStateOf(reminder.text) }
|
var text by remember { mutableStateOf(reminder.text) }
|
||||||
var selectedColor by remember { mutableIntStateOf(reminder.foregroundColor) }
|
var selectedColor by remember { mutableIntStateOf(reminder.foregroundColor) }
|
||||||
val colorDialogState by remember { mutableStateOf(UseCaseState()) }
|
val colorDialogState by remember { mutableStateOf(UseCaseState()) }
|
||||||
var duration by remember { mutableStateOf(reminder.interval.toString()) }
|
var duration by remember { mutableStateOf(reminder.interval.toString()) }
|
||||||
var isActive by remember { mutableStateOf(reminder.isActive) }
|
var isActive by remember { mutableStateOf(reminder.isActive) }
|
||||||
|
var autoDismiss by remember { mutableStateOf(reminder.isAutoDismiss) }
|
||||||
var deleteDialogVisible by remember { mutableStateOf(false) }
|
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
|
Column(modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@ -116,11 +136,28 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
.shadow(5.dp, CircleShape)
|
.shadow(5.dp, CircleShape)
|
||||||
.width(40.dp), content = {})
|
.width(40.dp), content = {})
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
Spacer(modifier = Modifier.width(5.dp))
|
||||||
|
|
||||||
Text("Change Color")
|
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) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Switch(checked = isActive, onCheckedChange = { isActive = it})
|
Switch(checked = isActive, onCheckedChange = { isActive = it})
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
@ -133,6 +170,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
onSubmit(getUpdatedReminder())
|
onSubmit(getUpdatedReminder())
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Default.Done, contentDescription = "Save Reminder")
|
Icon(Icons.Default.Done, contentDescription = "Save Reminder")
|
||||||
|
Spacer(modifier = Modifier.width(5.dp))
|
||||||
Text("Save")
|
Text("Save")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +180,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
onCancel()
|
onCancel()
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Default.Close, contentDescription = "Cancel Editing")
|
Icon(Icons.Default.Close, contentDescription = "Cancel Editing")
|
||||||
|
Spacer(modifier = Modifier.width(5.dp))
|
||||||
Text("Cancel")
|
Text("Cancel")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +208,64 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
},
|
},
|
||||||
title = { Text("Delete reminder") }, text = { Text("Really delete this reminder?") })
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package de.timklge.karooreminder.screens
|
package de.timklge.karooreminder.screens
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -86,9 +87,15 @@ fun ReminderAppNavHost(modifier: Modifier = Modifier, navController: NavHostCont
|
|||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
ctx.dataStore.data.distinctUntilChanged().collect { t ->
|
ctx.dataStore.data.distinctUntilChanged().collect { t ->
|
||||||
val entries = Json.decodeFromString<MutableList<Reminder>>(t[preferencesKey] ?: defaultReminders)
|
|
||||||
reminders.clear()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,14 +3,32 @@ package de.timklge.karooreminder.screens
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import de.timklge.karooreminder.R
|
import de.timklge.karooreminder.R
|
||||||
|
import io.hammerhead.karooext.models.PlayBeepPattern
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
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
|
@Serializable
|
||||||
class Reminder(val id: Int, var name: String, var interval: Int, var text: String,
|
class Reminder(val id: Int, var name: String, var interval: Int, var text: String,
|
||||||
var foregroundColor: Int = android.graphics.Color.parseColor("#700000"),
|
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 {
|
fun getResourceColor(context: Context): Int {
|
||||||
return when(foregroundColor){
|
return when(foregroundColor){
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user