Add profile selection dialog (#54)
* Setup BASE_URL ci config * Add profile selection dialog * Update changelog * Enable pipeline for all branches * Fix BASE_URL * Add readme info * Update button styling * Only use profile selection dialog to input new profiles * Update button colors
This commit is contained in:
parent
e68d4fabca
commit
3cc97ea85f
7
.github/workflows/android.yml
vendored
7
.github/workflows/android.yml
vendored
@ -3,10 +3,10 @@ name: Build
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ "master" ]
|
branches: [ "**" ]
|
||||||
tags: [ "*" ]
|
tags: [ "*" ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "master" ]
|
branches: [ "**" ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@ -26,7 +26,7 @@ jobs:
|
|||||||
echo "KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
|
echo "KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
|
||||||
echo "KEYSTORE_BASE64=${{ secrets.KEYSTORE_BASE64 }}" >> $GITHUB_ENV
|
echo "KEYSTORE_BASE64=${{ secrets.KEYSTORE_BASE64 }}" >> $GITHUB_ENV
|
||||||
echo "BUILD_NUMBER=${{ github.run_number }}" >> $GITHUB_ENV
|
echo "BUILD_NUMBER=${{ github.run_number }}" >> $GITHUB_ENV
|
||||||
echo "BASE_URL=${{ secrets.BASE_URL || '"https://github.com/timklge/karoo-powerbar/releases/latest/download"' }}" >> $GITHUB_ENV
|
echo "BASE_URL=${{ secrets.BASE_URL || 'https://github.com/timklge/karoo-reminder/releases/latest/download' }}" >> $GITHUB_ENV
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: set up JDK 17
|
- name: set up JDK 17
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
@ -61,3 +61,4 @@ jobs:
|
|||||||
list.png
|
list.png
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
Karoo extension that displays in-ride alerts based on custom triggers. Reminders can be set to activate after a specific time interval, distance traveled, energy output or when a sensor value is outside a defined range (e.g., heart rate exceeds zone 2).
|
Karoo extension that displays in-ride alerts based on custom triggers. Reminders can be set to activate after a specific time interval, distance traveled, energy output or when a sensor value is outside a defined range (e.g., heart rate exceeds zone 2).
|
||||||
|
|
||||||
|
By default, created reminders are active for all ride profiles. If you want to limit reminders to specific ride profiles (e. g. your "Gravel" profile), you can do so in the reminder settings.
|
||||||
|
|
||||||
Compatible with Karoo 2 and Karoo 3 devices.
|
Compatible with Karoo 2 and Karoo 3 devices.
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@ -73,7 +73,7 @@ tasks.register("generateManifest") {
|
|||||||
"latestVersionCode" to android.defaultConfig.versionCode,
|
"latestVersionCode" to android.defaultConfig.versionCode,
|
||||||
"developer" to "github.com/timklge",
|
"developer" to "github.com/timklge",
|
||||||
"description" to "Open-source extension that shows in-ride alerts after a given time interval has passed, distance has been traveled or HR / power / speed / cadence range is exceeded",
|
"description" to "Open-source extension that shows in-ride alerts after a given time interval has passed, distance has been traveled or HR / power / speed / cadence range is exceeded",
|
||||||
"releaseNotes" to "* Add rolling average setting for power triggers",
|
"releaseNotes" to "* Add profile selection dialog per reminder\n* Add rolling average setting for power triggers",
|
||||||
"screenshotUrls" to listOf(
|
"screenshotUrls" to listOf(
|
||||||
"$baseUrl/reminder.png",
|
"$baseUrl/reminder.png",
|
||||||
"$baseUrl/list.png",
|
"$baseUrl/list.png",
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import de.timklge.karooreminder.screens.Reminder
|
|||||||
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
|
||||||
|
import io.hammerhead.karooext.models.ActiveRideProfile
|
||||||
import io.hammerhead.karooext.models.OnStreamState
|
import io.hammerhead.karooext.models.OnStreamState
|
||||||
import io.hammerhead.karooext.models.RideState
|
import io.hammerhead.karooext.models.RideState
|
||||||
import io.hammerhead.karooext.models.StreamState
|
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> {
|
fun KarooSystemService.streamUserProfile(): Flow<UserProfile> {
|
||||||
return callbackFlow {
|
return callbackFlow {
|
||||||
val listenerId = addConsumer { userProfile: UserProfile ->
|
val listenerId = addConsumer { userProfile: UserProfile ->
|
||||||
|
|||||||
@ -5,8 +5,10 @@ import android.media.MediaPlayer
|
|||||||
import android.util.Log
|
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.ReminderBeepPattern
|
||||||
|
import de.timklge.karooreminder.screens.reminderIsActive
|
||||||
import io.hammerhead.karooext.KarooSystemService
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
import io.hammerhead.karooext.extension.KarooExtension
|
import io.hammerhead.karooext.extension.KarooExtension
|
||||||
|
import io.hammerhead.karooext.models.ActiveRideProfile
|
||||||
import io.hammerhead.karooext.models.DataType
|
import io.hammerhead.karooext.models.DataType
|
||||||
import io.hammerhead.karooext.models.HardwareType
|
import io.hammerhead.karooext.models.HardwareType
|
||||||
import io.hammerhead.karooext.models.InRideAlert
|
import io.hammerhead.karooext.models.InRideAlert
|
||||||
@ -231,13 +233,19 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
|||||||
|
|
||||||
val triggerJobs = mutableSetOf<Job>()
|
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 {
|
triggerStreamJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
streamPreferences().collect { reminders ->
|
streamDataFlow.collect { (reminders, activeRideProfile) ->
|
||||||
triggerJobs.forEach { it.cancel() }
|
triggerJobs.forEach { it.cancel() }
|
||||||
triggerJobs.clear()
|
triggerJobs.clear()
|
||||||
|
|
||||||
if (reminders.any { it.trigger == ReminderTrigger.DISTANCE }){
|
if (reminders.any { it.trigger == ReminderTrigger.DISTANCE }){
|
||||||
val distanceJob = startIntervalJob(ReminderTrigger.DISTANCE) {
|
val distanceJob = startIntervalJob(reminders, activeRideProfile, ReminderTrigger.DISTANCE) {
|
||||||
karooSystem.streamDataFlow(DataType.Type.DISTANCE)
|
karooSystem.streamDataFlow(DataType.Type.DISTANCE)
|
||||||
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||||
.combine(karooSystem.streamUserProfile()) { distance, profile -> distance to profile }
|
.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 }){
|
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)
|
karooSystem.streamDataFlow(DataType.Type.ELAPSED_TIME)
|
||||||
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||||
.map { (it / 1000 / 60).toInt() }
|
.map { (it / 1000 / 60).toInt() }
|
||||||
@ -266,7 +274,7 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reminders.any { it.trigger == ReminderTrigger.ENERGY_OUTPUT }){
|
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)
|
karooSystem.streamDataFlow(DataType.Type.ENERGY_OUTPUT)
|
||||||
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||||
.map { it.toInt() }
|
.map { it.toInt() }
|
||||||
@ -301,7 +309,7 @@ class KarooReminderExtension : KarooExtension("karoo-reminder", BuildConfig.VERS
|
|||||||
intervalTriggers.forEach { trigger ->
|
intervalTriggers.forEach { trigger ->
|
||||||
SmoothSetting.entries.forEach { smoothSetting ->
|
SmoothSetting.entries.forEach { smoothSetting ->
|
||||||
if (reminders.any { it.trigger == trigger && it.smoothSetting == 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)
|
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 {
|
return CoroutineScope(Dispatchers.IO).launch {
|
||||||
val preferences = streamPreferences()
|
|
||||||
|
|
||||||
flow()
|
flow()
|
||||||
.filterNot { it == 0 }
|
.filterNot { it == 0 }
|
||||||
.combine(preferences) { elapsedMinutes, reminders -> elapsedMinutes to reminders}
|
.distinctUntilChanged()
|
||||||
.distinctUntilChanged { old, new -> old.first == new.first }
|
.collectLatest { elapsedMinutes ->
|
||||||
.collectLatest { (elapsedMinutes, reminders) ->
|
val rs = preferences
|
||||||
val rs = reminders
|
|
||||||
.filter { reminder ->
|
.filter { reminder ->
|
||||||
val interval = reminder.interval
|
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){
|
for (reminder in rs) {
|
||||||
Log.i(TAG, "$trigger reminder: ${reminder.name}")
|
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}",
|
id = "reminder-${reminder.id}-${elapsedMinutes}",
|
||||||
detail = reminder.text,
|
detail = reminder.text,
|
||||||
title = reminder.name,
|
title = reminder.name,
|
||||||
autoDismissMs = if(reminder.isAutoDismiss) reminder.autoDismissSeconds * 1000L else null,
|
autoDismissMs = if (reminder.isAutoDismiss) reminder.autoDismissSeconds * 1000L else null,
|
||||||
icon = R.drawable.timer,
|
icon = R.drawable.timer,
|
||||||
textColor = reminder.displayForegroundColor?.getTextColor() ?: R.color.black,
|
textColor = reminder.displayForegroundColor?.getTextColor()
|
||||||
backgroundColor = reminder.displayForegroundColor?.colorRes ?: R.color.hRed
|
?: 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 {
|
return CoroutineScope(Dispatchers.IO).launch {
|
||||||
val preferences = streamPreferences()
|
|
||||||
|
|
||||||
Log.i(TAG, "Starting range exceeded job for trigger $triggerType with smooth setting $smoothSetting")
|
Log.i(TAG, "Starting range exceeded job for trigger $triggerType with smooth setting $smoothSetting")
|
||||||
|
|
||||||
val valueStream = karooSystem.streamDataFlow(triggerType.getSmoothedDataType(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)
|
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,
|
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 {
|
}.filter {
|
||||||
it.rideState is RideState.Recording
|
it.rideState is RideState.Recording
|
||||||
}.let {
|
}.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")
|
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
|
triggered
|
||||||
|
|||||||
@ -18,6 +18,7 @@ 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.Add
|
||||||
import androidx.compose.material.icons.filled.Build
|
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
|
||||||
@ -25,6 +26,7 @@ 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.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
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
|
||||||
@ -103,6 +105,8 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
var selectedTone by remember { mutableStateOf(reminder.tone) }
|
var selectedTone by remember { mutableStateOf(reminder.tone) }
|
||||||
var autoDismissSeconds by remember { mutableStateOf(reminder.autoDismissSeconds.toString()) }
|
var autoDismissSeconds by remember { mutableStateOf(reminder.autoDismissSeconds.toString()) }
|
||||||
var selectedTrigger by remember { mutableStateOf(reminder.trigger) }
|
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)
|
val profile by karooSystem.streamUserProfile().collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
@ -116,7 +120,8 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
isActive = isActive,
|
isActive = isActive,
|
||||||
smoothSetting = smoothSetting,
|
smoothSetting = smoothSetting,
|
||||||
trigger = selectedTrigger,
|
trigger = selectedTrigger,
|
||||||
isAutoDismiss = autoDismiss, tone = selectedTone, autoDismissSeconds = autoDismissSeconds.toIntOrNull() ?: 15)
|
isAutoDismiss = autoDismiss, tone = selectedTone, autoDismissSeconds = autoDismissSeconds.toIntOrNull() ?: 15,
|
||||||
|
enabledRideProfiles = enabledRideProfiles.toSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = Modifier
|
Column(modifier = Modifier
|
||||||
@ -259,6 +264,41 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
if (enabledRideProfiles.isEmpty()) {
|
||||||
|
Text("Enabled for all profiles")
|
||||||
|
} else {
|
||||||
|
Text("Enabled for profiles:")
|
||||||
|
|
||||||
|
enabledRideProfiles.forEach { profileName ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(profileName)
|
||||||
|
FilledTonalButton(onClick = {
|
||||||
|
enabledRideProfiles = enabledRideProfiles.toMutableSet().apply { remove(profileName) }
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.Delete, contentDescription = "Delete profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilledTonalButton(modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(60.dp), onClick = {
|
||||||
|
rideProfileDialogVisible = true
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.Build, contentDescription = "Change Profiles", modifier = Modifier.size(20.dp))
|
||||||
|
Spacer(modifier = Modifier.width(5.dp))
|
||||||
|
Text("Limit to Profile")
|
||||||
|
}
|
||||||
|
|
||||||
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))
|
||||||
@ -310,6 +350,59 @@ 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 (rideProfileDialogVisible) {
|
||||||
|
var newProfileName by remember { mutableStateOf("") }
|
||||||
|
Dialog(onDismissRequest = { rideProfileDialogVisible = false }) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(10.dp),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newProfileName,
|
||||||
|
onValueChange = { newProfileName = it },
|
||||||
|
label = { Text("New profile name") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = {
|
||||||
|
if (newProfileName.isNotBlank()) {
|
||||||
|
enabledRideProfiles = enabledRideProfiles.toMutableSet().apply { add(newProfileName) }
|
||||||
|
newProfileName = ""
|
||||||
|
rideProfileDialogVisible = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth().height(60.dp)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Add, contentDescription = "Add Profile")
|
||||||
|
Spacer(modifier = Modifier.width(5.dp))
|
||||||
|
Text("Add Profile")
|
||||||
|
}
|
||||||
|
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = { rideProfileDialogVisible = false },
|
||||||
|
modifier = Modifier.fillMaxWidth().height(60.dp)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Close, contentDescription = "Cancel Editing")
|
||||||
|
Spacer(modifier = Modifier.width(5.dp))
|
||||||
|
Text("Close")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (triggerDialogVisible){
|
if (triggerDialogVisible){
|
||||||
Dialog(onDismissRequest = { triggerDialogVisible = false }) {
|
Dialog(onDismissRequest = { triggerDialogVisible = false }) {
|
||||||
Card(
|
Card(
|
||||||
@ -317,6 +410,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(10.dp),
|
.padding(10.dp),
|
||||||
shape = RoundedCornerShape(10.dp),
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier
|
Column(modifier = Modifier
|
||||||
.padding(5.dp)
|
.padding(5.dp)
|
||||||
@ -361,6 +455,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(10.dp),
|
.padding(10.dp),
|
||||||
shape = RoundedCornerShape(10.dp),
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier
|
Column(modifier = Modifier
|
||||||
.padding(5.dp)
|
.padding(5.dp)
|
||||||
@ -396,6 +491,7 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(10.dp),
|
.padding(10.dp),
|
||||||
shape = RoundedCornerShape(10.dp),
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier
|
Column(modifier = Modifier
|
||||||
.padding(5.dp)
|
.padding(5.dp)
|
||||||
@ -430,3 +526,4 @@ fun DetailScreen(isCreating: Boolean, reminder: Reminder, onSubmit: (updatedRemi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,13 +20,8 @@ import androidx.compose.foundation.shape.CircleShape
|
|||||||
import androidx.compose.foundation.shape.CornerSize
|
import androidx.compose.foundation.shape.CornerSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.rounded.Add
|
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FabPosition
|
|
||||||
import androidx.compose.material3.FloatingActionButton
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@ -63,6 +58,7 @@ import androidx.navigation.navArgument
|
|||||||
import de.timklge.karooreminder.KarooReminderExtension
|
import de.timklge.karooreminder.KarooReminderExtension
|
||||||
import de.timklge.karooreminder.R
|
import de.timklge.karooreminder.R
|
||||||
import de.timklge.karooreminder.dataStore
|
import de.timklge.karooreminder.dataStore
|
||||||
|
import de.timklge.karooreminder.streamActiveRideProfile
|
||||||
import de.timklge.karooreminder.streamUserProfile
|
import de.timklge.karooreminder.streamUserProfile
|
||||||
import io.hammerhead.karooext.KarooSystemService
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
import io.hammerhead.karooext.models.UserProfile
|
import io.hammerhead.karooext.models.UserProfile
|
||||||
@ -72,7 +68,6 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
|
||||||
val preferencesKey = stringPreferencesKey("reminders")
|
val preferencesKey = stringPreferencesKey("reminders")
|
||||||
|
|
||||||
suspend fun saveReminders(context: Context, reminders: MutableList<Reminder>) {
|
suspend fun saveReminders(context: Context, reminders: MutableList<Reminder>) {
|
||||||
@ -165,6 +160,7 @@ fun MainScreen(reminders: MutableList<Reminder>, onNavigateToReminder: (r: Remin
|
|||||||
|
|
||||||
var showWarnings by remember { mutableStateOf(false) }
|
var showWarnings by remember { mutableStateOf(false) }
|
||||||
val profile by karooSystem.streamUserProfile().collectAsStateWithLifecycle(null)
|
val profile by karooSystem.streamUserProfile().collectAsStateWithLifecycle(null)
|
||||||
|
val currentRideProfile by karooSystem.streamActiveRideProfile().collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
delay(1000L)
|
delay(1000L)
|
||||||
@ -197,7 +193,7 @@ fun MainScreen(reminders: MutableList<Reminder>, onNavigateToReminder: (r: Remin
|
|||||||
Card(Modifier
|
Card(Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(60.dp)
|
.height(60.dp)
|
||||||
.alpha(if (reminder.isActive) 1f else 0.6f)
|
.alpha(if (reminderIsActive(reminder, currentRideProfile?.profile)) 1f else 0.6f)
|
||||||
.clickable { onNavigateToReminder(reminder) }
|
.clickable { onNavigateToReminder(reminder) }
|
||||||
.padding(5.dp), shape = RoundedCornerShape(corner = CornerSize(10.dp))
|
.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.ReminderTrigger
|
||||||
import de.timklge.karooreminder.SmoothSetting
|
import de.timklge.karooreminder.SmoothSetting
|
||||||
import io.hammerhead.karooext.models.PlayBeepPattern
|
import io.hammerhead.karooext.models.PlayBeepPattern
|
||||||
|
import io.hammerhead.karooext.models.RideProfile
|
||||||
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
|
||||||
@ -141,6 +142,14 @@ class Reminder(val id: Int, var name: String,
|
|||||||
val isActive: Boolean = true, val isAutoDismiss: Boolean = true,
|
val isActive: Boolean = true, val isAutoDismiss: Boolean = true,
|
||||||
val tone: ReminderBeepPattern = ReminderBeepPattern.THREE_TONES_UP,
|
val tone: ReminderBeepPattern = ReminderBeepPattern.THREE_TONES_UP,
|
||||||
var trigger: ReminderTrigger = ReminderTrigger.ELAPSED_TIME,
|
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!")))
|
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 || enabledRideProfiles.isEmpty() || enabledRideProfiles.contains(currentProfileName))
|
||||||
|
}
|
||||||
@ -22,7 +22,7 @@ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "
|
|||||||
[libraries]
|
[libraries]
|
||||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||||
color = { module = "com.maxkeppeler.sheets-compose-dialogs:color", version.ref = "color" }
|
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" }
|
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user