Add profile selection dialog
This commit is contained in:
parent
e68d4fabca
commit
bf500596fb
@ -7,6 +7,7 @@ import de.timklge.karooreminder.screens.Reminder
|
||||
import de.timklge.karooreminder.screens.defaultReminders
|
||||
import de.timklge.karooreminder.screens.preferencesKey
|
||||
import io.hammerhead.karooext.KarooSystemService
|
||||
import io.hammerhead.karooext.models.ActiveRideProfile
|
||||
import io.hammerhead.karooext.models.OnStreamState
|
||||
import io.hammerhead.karooext.models.RideState
|
||||
import io.hammerhead.karooext.models.StreamState
|
||||
@ -40,6 +41,17 @@ fun KarooSystemService.streamRideState(): Flow<RideState> {
|
||||
}
|
||||
}
|
||||
|
||||
fun KarooSystemService.streamActiveRideProfile(): Flow<ActiveRideProfile> {
|
||||
return callbackFlow {
|
||||
val listenerId = addConsumer { activeProfile: ActiveRideProfile ->
|
||||
trySendBlocking(activeProfile)
|
||||
}
|
||||
awaitClose {
|
||||
removeConsumer(listenerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun KarooSystemService.streamUserProfile(): Flow<UserProfile> {
|
||||
return callbackFlow {
|
||||
val listenerId = addConsumer { userProfile: UserProfile ->
|
||||
|
||||
@ -5,8 +5,10 @@ import android.media.MediaPlayer
|
||||
import android.util.Log
|
||||
import de.timklge.karooreminder.screens.Reminder
|
||||
import de.timklge.karooreminder.screens.ReminderBeepPattern
|
||||
import de.timklge.karooreminder.screens.reminderIsActive
|
||||
import io.hammerhead.karooext.KarooSystemService
|
||||
import io.hammerhead.karooext.extension.KarooExtension
|
||||
import io.hammerhead.karooext.models.ActiveRideProfile
|
||||
import io.hammerhead.karooext.models.DataType
|
||||
import io.hammerhead.karooext.models.HardwareType
|
||||
import io.hammerhead.karooext.models.InRideAlert
|
||||
@ -231,13 +233,19 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
||||
|
||||
val triggerJobs = mutableSetOf<Job>()
|
||||
|
||||
data class StreamData(val preferences: MutableList<Reminder>, val activeProfile: ActiveRideProfile)
|
||||
|
||||
val streamDataFlow = combine(streamPreferences(), karooSystem.streamActiveRideProfile()) { reminders, activeProfile ->
|
||||
StreamData(preferences = reminders, activeProfile = activeProfile)
|
||||
}
|
||||
|
||||
triggerStreamJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
streamPreferences().collect { reminders ->
|
||||
streamDataFlow.collect { (reminders, activeRideProfile) ->
|
||||
triggerJobs.forEach { it.cancel() }
|
||||
triggerJobs.clear()
|
||||
|
||||
if (reminders.any { it.trigger == ReminderTrigger.DISTANCE }){
|
||||
val distanceJob = startIntervalJob(ReminderTrigger.DISTANCE) {
|
||||
val distanceJob = startIntervalJob(reminders, activeRideProfile, ReminderTrigger.DISTANCE) {
|
||||
karooSystem.streamDataFlow(DataType.Type.DISTANCE)
|
||||
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||
.combine(karooSystem.streamUserProfile()) { distance, profile -> distance to profile }
|
||||
@ -255,7 +263,7 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
||||
}
|
||||
|
||||
if (reminders.any { it.trigger == ReminderTrigger.ELAPSED_TIME }){
|
||||
val elapsedTimeJob = startIntervalJob(ReminderTrigger.ELAPSED_TIME) {
|
||||
val elapsedTimeJob = startIntervalJob(reminders, activeRideProfile, ReminderTrigger.ELAPSED_TIME) {
|
||||
karooSystem.streamDataFlow(DataType.Type.ELAPSED_TIME)
|
||||
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||
.map { (it / 1000 / 60).toInt() }
|
||||
@ -266,7 +274,7 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
||||
}
|
||||
|
||||
if (reminders.any { it.trigger == ReminderTrigger.ENERGY_OUTPUT }){
|
||||
val energyOutputJob = startIntervalJob(ReminderTrigger.ENERGY_OUTPUT) {
|
||||
val energyOutputJob = startIntervalJob(reminders, activeRideProfile, ReminderTrigger.ENERGY_OUTPUT) {
|
||||
karooSystem.streamDataFlow(DataType.Type.ENERGY_OUTPUT)
|
||||
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||
.map { it.toInt() }
|
||||
@ -301,7 +309,7 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
||||
intervalTriggers.forEach { trigger ->
|
||||
SmoothSetting.entries.forEach { smoothSetting ->
|
||||
if (reminders.any { it.trigger == trigger && it.smoothSetting == smoothSetting }){
|
||||
val job = startRangeExceededJob(trigger, smoothSetting)
|
||||
val job = startRangeExceededJob(reminders, activeRideProfile, trigger, smoothSetting)
|
||||
triggerJobs.add(job)
|
||||
}
|
||||
}
|
||||
@ -310,41 +318,43 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
||||
}
|
||||
}
|
||||
|
||||
private fun startIntervalJob(trigger: ReminderTrigger, flow: () -> Flow<Int>): Job {
|
||||
private fun startIntervalJob(preferences: List<Reminder>, activeRideProfile: ActiveRideProfile, trigger: ReminderTrigger, flow: () -> Flow<Int>): Job {
|
||||
return CoroutineScope(Dispatchers.IO).launch {
|
||||
val preferences = streamPreferences()
|
||||
|
||||
flow()
|
||||
.filterNot { it == 0 }
|
||||
.combine(preferences) { elapsedMinutes, reminders -> elapsedMinutes to reminders}
|
||||
.distinctUntilChanged { old, new -> old.first == new.first }
|
||||
.collectLatest { (elapsedMinutes, reminders) ->
|
||||
val rs = reminders
|
||||
.distinctUntilChanged()
|
||||
.collectLatest { elapsedMinutes ->
|
||||
val rs = preferences
|
||||
.filter { reminder ->
|
||||
val interval = reminder.interval
|
||||
reminder.trigger == trigger && reminder.isActive && interval != null && elapsedMinutes % interval == 0
|
||||
reminder.trigger == trigger && reminderIsActive(reminder, activeRideProfile.profile) && interval != null && elapsedMinutes % interval == 0
|
||||
}
|
||||
|
||||
for (reminder in rs) {
|
||||
Log.i(TAG, "$trigger reminder: ${reminder.name}")
|
||||
reminderChannel.send(DisplayedReminder(reminder.tone, trigger, InRideAlert(
|
||||
reminderChannel.send(
|
||||
DisplayedReminder(
|
||||
reminder.tone, trigger, InRideAlert(
|
||||
id = "reminder-${reminder.id}-${elapsedMinutes}",
|
||||
detail = reminder.text,
|
||||
title = reminder.name,
|
||||
autoDismissMs = if (reminder.isAutoDismiss) reminder.autoDismissSeconds * 1000L else null,
|
||||
icon = R.drawable.timer,
|
||||
textColor = reminder.displayForegroundColor?.getTextColor() ?: R.color.black,
|
||||
backgroundColor = reminder.displayForegroundColor?.colorRes ?: R.color.hRed
|
||||
)))
|
||||
textColor = reminder.displayForegroundColor?.getTextColor()
|
||||
?: R.color.black,
|
||||
backgroundColor = reminder.displayForegroundColor?.colorRes
|
||||
?: R.color.hRed
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startRangeExceededJob(triggerType: ReminderTrigger, smoothSetting: SmoothSetting): Job {
|
||||
private fun startRangeExceededJob(preferences: MutableList<Reminder>, activeRideProfile: ActiveRideProfile, triggerType: ReminderTrigger, smoothSetting: SmoothSetting): Job {
|
||||
return CoroutineScope(Dispatchers.IO).launch {
|
||||
val preferences = streamPreferences()
|
||||
|
||||
Log.i(TAG, "Starting range exceeded job for trigger $triggerType with smooth setting $smoothSetting")
|
||||
|
||||
val valueStream = karooSystem.streamDataFlow(triggerType.getSmoothedDataType(smoothSetting))
|
||||
@ -370,9 +380,9 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
||||
|
||||
data class StreamData(val value: Double, val reminders: MutableList<Reminder>, val distanceImperial: Boolean, val temperatureImperial: Boolean, val rideState: RideState)
|
||||
|
||||
combine(valueStream, preferences, karooSystem.streamUserProfile(), karooSystem.streamRideState()) { value, reminders, profile, rideState ->
|
||||
combine(valueStream, karooSystem.streamUserProfile(), karooSystem.streamRideState()) { value, profile, rideState ->
|
||||
StreamData(distanceImperial = profile.preferredUnit.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL, temperatureImperial = profile.preferredUnit.temperature == UserProfile.PreferredUnit.UnitType.IMPERIAL,
|
||||
value = value, reminders = reminders, rideState = rideState)
|
||||
value = value, reminders = preferences, rideState = rideState)
|
||||
}.filter {
|
||||
it.rideState is RideState.Recording
|
||||
}.let {
|
||||
@ -432,7 +442,7 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
||||
ReminderTrigger.ELAPSED_TIME, ReminderTrigger.DISTANCE, ReminderTrigger.ENERGY_OUTPUT -> error("Unsupported trigger type: $triggerType")
|
||||
}
|
||||
|
||||
reminder.isActive && reminder.trigger == triggerType && triggerIsMet
|
||||
reminderIsActive(reminder, activeRideProfile.profile) && reminder.trigger == triggerType && triggerIsMet
|
||||
}
|
||||
|
||||
triggered
|
||||
|
||||
@ -103,6 +103,8 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
||||
var selectedTone by remember { mutableStateOf(reminder.tone) }
|
||||
var autoDismissSeconds by remember { mutableStateOf(reminder.autoDismissSeconds.toString()) }
|
||||
var selectedTrigger by remember { mutableStateOf(reminder.trigger) }
|
||||
var rideProfileDialogVisible by remember { mutableStateOf(false) }
|
||||
var enabledRideProfiles by remember { mutableStateOf(reminder.enabledRideProfiles.toMutableSet()) }
|
||||
|
||||
val profile by karooSystem.streamUserProfile().collectAsStateWithLifecycle(null)
|
||||
|
||||
@ -116,7 +118,8 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
||||
isActive = isActive,
|
||||
smoothSetting = smoothSetting,
|
||||
trigger = selectedTrigger,
|
||||
isAutoDismiss = autoDismiss, tone = selectedTone, autoDismissSeconds = autoDismissSeconds.toIntOrNull() ?: 15)
|
||||
isAutoDismiss = autoDismiss, tone = selectedTone, autoDismissSeconds = autoDismissSeconds.toIntOrNull() ?: 15,
|
||||
enabledRideProfiles = enabledRideProfiles.toSet())
|
||||
}
|
||||
|
||||
Column(modifier = Modifier
|
||||
@ -265,6 +268,16 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
||||
Text("Is Active")
|
||||
}
|
||||
|
||||
FilledTonalButton(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp), onClick = {
|
||||
rideProfileDialogVisible = true
|
||||
}) {
|
||||
Icon(Icons.Default.Build, contentDescription = "Change Ride Profiles", modifier = Modifier.size(20.dp))
|
||||
Spacer(modifier = Modifier.width(5.dp))
|
||||
Text("Ride Profiles: ${if (enabledRideProfiles.isEmpty()) "All" else enabledRideProfiles.joinToString(", ")}")
|
||||
}
|
||||
|
||||
FilledTonalButton(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp), onClick = {
|
||||
@ -310,6 +323,79 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
||||
title = { Text("Delete reminder") }, text = { Text("Really delete this reminder?") })
|
||||
}
|
||||
|
||||
if (rideProfileDialogVisible) {
|
||||
var newProfileName by remember { mutableStateOf("") }
|
||||
Dialog(onDismissRequest = { rideProfileDialogVisible = false }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
if (enabledRideProfiles.isEmpty()) {
|
||||
Text("All profiles enabled")
|
||||
} else {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
enabledRideProfiles.forEach { profileName ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(profileName)
|
||||
Button(onClick = {
|
||||
enabledRideProfiles = enabledRideProfiles.toMutableSet().apply { remove(profileName) }
|
||||
}) {
|
||||
Icon(Icons.Default.Delete, contentDescription = "Delete profile")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = newProfileName,
|
||||
onValueChange = { newProfileName = it },
|
||||
label = { Text("New profile name") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true
|
||||
)
|
||||
Button(
|
||||
onClick = {
|
||||
if (newProfileName.isNotBlank()) {
|
||||
enabledRideProfiles = enabledRideProfiles.toMutableSet().apply { add(newProfileName) }
|
||||
newProfileName = "" // Clear the text field
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text("Add Profile")
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
Button(
|
||||
onClick = { rideProfileDialogVisible = false },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text("Close")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerDialogVisible){
|
||||
Dialog(onDismissRequest = { triggerDialogVisible = false }) {
|
||||
Card(
|
||||
@ -430,3 +516,4 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -63,6 +63,8 @@ import androidx.navigation.navArgument
|
||||
import de.timklge.karooreminder.KarooReminderExtension
|
||||
import de.timklge.karooreminder.R
|
||||
import de.timklge.karooreminder.dataStore
|
||||
import de.timklge.karooreminder.streamActiveRideProfile
|
||||
import de.timklge.karooreminder.streamRideState
|
||||
import de.timklge.karooreminder.streamUserProfile
|
||||
import io.hammerhead.karooext.KarooSystemService
|
||||
import io.hammerhead.karooext.models.UserProfile
|
||||
@ -165,6 +167,7 @@ fun MainScreen(reminders: MutableList<Reminder>, onNavigateToReminder: (r: Remin
|
||||
|
||||
var showWarnings by remember { mutableStateOf(false) }
|
||||
val profile by karooSystem.streamUserProfile().collectAsStateWithLifecycle(null)
|
||||
val currentRideProfile by karooSystem.streamActiveRideProfile().collectAsStateWithLifecycle(null)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(1000L)
|
||||
@ -197,7 +200,7 @@ fun MainScreen(reminders: MutableList<Reminder>, onNavigateToReminder: (r: Remin
|
||||
Card(Modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp)
|
||||
.alpha(if (reminder.isActive) 1f else 0.6f)
|
||||
.alpha(if (reminderIsActive(reminder, currentRideProfile?.profile)) 1f else 0.6f)
|
||||
.clickable { onNavigateToReminder(reminder) }
|
||||
.padding(5.dp), shape = RoundedCornerShape(corner = CornerSize(10.dp))
|
||||
) {
|
||||
|
||||
@ -7,6 +7,7 @@ import de.timklge.karooreminder.R
|
||||
import de.timklge.karooreminder.ReminderTrigger
|
||||
import de.timklge.karooreminder.SmoothSetting
|
||||
import io.hammerhead.karooext.models.PlayBeepPattern
|
||||
import io.hammerhead.karooext.models.RideProfile
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
@ -141,6 +142,14 @@ class Reminder(val id: Int, var name: String,
|
||||
val isActive: Boolean = true, val isAutoDismiss: Boolean = true,
|
||||
val tone: ReminderBeepPattern = ReminderBeepPattern.THREE_TONES_UP,
|
||||
var trigger: ReminderTrigger = ReminderTrigger.ELAPSED_TIME,
|
||||
val autoDismissSeconds: Int = 15)
|
||||
val autoDismissSeconds: Int = 15,
|
||||
val enabledRideProfiles: Set<String> = emptySet())
|
||||
|
||||
val defaultReminders = Json.encodeToString(listOf(Reminder(0, "Drink", 30, text = "Take a sip!")))
|
||||
|
||||
fun reminderIsActive(reminder: Reminder, currentRideProfile: RideProfile?): Boolean {
|
||||
val enabledRideProfiles = reminder.enabledRideProfiles.map { it.lowercase().trim() }
|
||||
val currentProfileName = currentRideProfile?.name?.lowercase()?.trim()
|
||||
|
||||
return reminder.isActive && (currentRideProfile == null || reminder.enabledRideProfiles.isEmpty() || enabledRideProfiles.contains(currentProfileName))
|
||||
}
|
||||
@ -22,7 +22,7 @@ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "
|
||||
[libraries]
|
||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||
color = { module = "com.maxkeppeler.sheets-compose-dialogs:color", version.ref = "color" }
|
||||
hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.1" }
|
||||
hammerhead-karoo-ext = { group = "io.hammerhead", name = "karoo-ext", version = "1.1.5" }
|
||||
|
||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user