Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions shared/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@
<string name="status_in_process">In process</string>
<string name="status_completed">Completed</string>

<string name="priority">Priority</string>
<string name="priority_not_specified">Not specified</string>
<string name="priority_low">Low</string>
<string name="priority_medium">Medium</string>
<string name="priority_high">High</string>

<string name="login_message_forbidden">Not authorized. Please check your server, username and password.</string>

<string name="welcome_to_app">Welcome to %1$s</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package at.techbee.spectacled.screens.core.domain

import org.jetbrains.compose.resources.StringResource
import spectacled.shared.generated.resources.Res
import spectacled.shared.generated.resources.priority_high
import spectacled.shared.generated.resources.priority_low
import spectacled.shared.generated.resources.priority_medium
import spectacled.shared.generated.resources.priority_not_specified

/**
* The coarse priority levels offered in the UI, ordered from lowest to highest so that
* [ordinal] can be used directly as a slider step.
*
* RFC 5545 (3.8.1.9) defines PRIORITY as 0 (undefined) or 1 (highest) to 9 (lowest) and groups
* 1-4 as high, 5 as medium and 6-9 as low. [icsValue] is the value written when a level is
* picked; [fromIcsValue] maps any stored value back to its level.
*/
enum class Priority(
val icsValue: Long?,
val stringRes: StringResource
) {
NOT_SPECIFIED(null, Res.string.priority_not_specified),
LOW(9L, Res.string.priority_low),
MEDIUM(5L, Res.string.priority_medium),
HIGH(1L, Res.string.priority_high);

companion object {
fun fromIcsValue(icsValue: Long?): Priority = when (icsValue) {
in 1L..4L -> HIGH
5L -> MEDIUM
in 6L..9L -> LOW
else -> NOT_SPECIFIED // null, 0 (undefined per RFC 5545) and out-of-range values
}

fun fromSliderStep(step: Int): Priority = entries.getOrElse(step) { NOT_SPECIFIED }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.QuestionMark
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Circle
import androidx.compose.material.icons.outlined.RemoveCircle
import androidx.compose.material.icons.outlined.Unpublished
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
Expand All @@ -26,7 +27,7 @@ import at.techbee.spectacled.theme.AppTheme

@Composable
fun StatusWithProgressIcon(
status: Status,
status: Status?,
percent: Long? = null,
) {

Expand All @@ -38,17 +39,17 @@ fun StatusWithProgressIcon(
}

val icon = when(status) {
Status.FINAL -> Icons.Outlined.Check
Status.DRAFT -> Icons.Outlined.QuestionMark
Status.CANCELLED -> Icons.Outlined.Close
else -> null
Status.FINAL -> Icons.Outlined.CheckCircle
Status.DRAFT -> Icons.Outlined.Unpublished
Status.CANCELLED -> Icons.Outlined.RemoveCircle
else -> Icons.Outlined.Circle
}

val progress = when(status) {
Status.FINAL, Status.COMPLETED -> 1f
Status.DRAFT -> 0.33f
Status.IN_PROCESS -> (percent?:1L)/100f
Status.NEEDS_ACTION, Status.CANCELLED -> 0f
null, Status.NEEDS_ACTION, Status.CANCELLED -> 0f
}

Box(contentAlignment = Alignment.Center) {
Expand All @@ -61,17 +62,12 @@ fun StatusWithProgressIcon(
fontWeight = FontWeight.Bold,
fontSize = 9.sp
)
}

icon?.let {
Icon(
imageVector = it,
contentDescription = null,
tint = ProgressIndicatorDefaults.circularColor,
modifier = Modifier.size(14.dp)
)
}

} ?: Icon(
imageVector = icon,
contentDescription = null,
tint = if(status == Status.CANCELLED) MaterialTheme.colorScheme.error else ProgressIndicatorDefaults.circularColor,
modifier = Modifier.size(14.dp)
)

CircularProgressIndicator(
progress = { progress },
Expand All @@ -85,6 +81,9 @@ fun StatusWithProgressIcon(
private fun StatusWithProgress_Icon_Preview() {
AppTheme(spectacledVariant = SpectacledVariant.JOURNALS) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {

StatusWithProgressIcon(status = null)

StatusWithProgressIcon(status = Status.FINAL)
StatusWithProgressIcon(status = Status.DRAFT)
StatusWithProgressIcon(status = Status.CANCELLED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ sealed interface DetailsAction {
data class OnUpdateDtStart(val icsDateTime: IcsDateTime?): DetailsAction
data class OnUpdateDue(val icsDateTime: IcsDateTime?): DetailsAction
data class OnUpdateProgress(val percent: Long): DetailsAction
data class OnUpdatePriority(val priority: Long?): DetailsAction
data class OnUpdateSubtaskProgress(val percent: Long, val subtaskIcalEntryId: Long): DetailsAction
data class OnAddSubtask(val summary: String): DetailsAction
data class OnUpdateUrl(val url: Url?): DetailsAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import at.techbee.spectacled.screens.Route.IcalEntryDetails
import at.techbee.spectacled.screens.core.Platforms
import at.techbee.spectacled.screens.core.domain.CalendarComponent
import at.techbee.spectacled.screens.core.domain.IcalEntry
import at.techbee.spectacled.screens.core.domain.Status
import at.techbee.spectacled.screens.core.domain.SyncState
import at.techbee.spectacled.screens.core.getPlatform
import at.techbee.spectacled.screens.core.presentation.components.BottomSheetWithMenu
Expand Down Expand Up @@ -236,9 +235,11 @@ fun DetailsScreenRoot(
TaskStatusProgressPickerBottomSheet(
status = detailsState.icalEntry.status,
percentComplete = detailsState.icalEntry.percentComplete,
priority = detailsState.icalEntry.priority,
sheetState = rememberBottomSheetState(initialValue = SheetValue.Expanded),
onStatusUpdated = { detailsViewModel.onAction(DetailsAction.OnUpdateStatus(it)) },
onProgressUpdated = { detailsViewModel.onAction(DetailsAction.OnUpdateProgress(it)) },
onPriorityUpdated = { detailsViewModel.onAction(DetailsAction.OnUpdatePriority(it)) },
onDismiss = { detailsViewModel.onAction(DetailsAction.OnShowSheetOrDialog(null)) }
)
DetailsSheetOrDialog.ADD_SUBTASKS ->
Expand Down Expand Up @@ -386,8 +387,7 @@ fun DetailsScreenRoot(
enabled = detailsState.allowEditing() && !detailsState.isLoading
) {
StatusWithProgressIcon(
status = detailsState.icalEntry.status
?: if(detailsState.icalEntry.isTask()) Status.NEEDS_ACTION else Status.FINAL,
status = detailsState.icalEntry.status,
percent = detailsState.icalEntry.percentComplete
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ class DetailsViewModel(
is DetailsAction.OnPin -> { onPinIcalEntry(action.pin) }
is DetailsAction.OnUpdateStatus -> { onUpdateStatus(action.status) }
is DetailsAction.OnUpdateProgress -> { onUpdateTaskProgress(action.percent) }
is DetailsAction.OnUpdatePriority -> { onUpdatePriority(action.priority) }
is DetailsAction.OnUpdateSubtaskProgress -> { onUpdateSubtaskProgress(action.percent, action.subtaskIcalEntryId) }
is DetailsAction.OnAddSubtask -> { insertSubtask(action.summary) }
is DetailsAction.OnNavigateToIcalEntryId -> { _state.update { it.copy(navigateToIcalEntryId = action.id) } }
Expand Down Expand Up @@ -482,6 +483,22 @@ class DetailsViewModel(
}
}

private fun onUpdatePriority(newPriority: Long?) {

if(!state.value.allowEditing())
return

_state.update {
it.copy(
icalEntry = it.icalEntry.copy(
priority = newPriority,
lastModified = IcsDateTime.now(),
syncState = it.icalEntry.syncState.afterLocalEdit()
)
)
}
}


private fun onUpdateColor(newColor: Color?) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp
import at.techbee.spectacled.SpectacledVariant
import at.techbee.spectacled.screens.core.domain.Status
import at.techbee.spectacled.screens.core.presentation.components.BottomSheetWithMenu
import at.techbee.spectacled.screens.core.presentation.components.StatusWithProgressIcon
import at.techbee.spectacled.theme.AppTheme
import org.jetbrains.compose.resources.stringResource
import spectacled.shared.generated.resources.Res
Expand All @@ -45,10 +46,6 @@ fun JournalStatusPickerBottomSheet(
Text(stringResource(Res.string.done))
}
},
menuActionLeft = { TextButton(onClick = {
onStatusUpdated(null)
onDismiss()
}) { Text(stringResource(Res.string.status_no_status)) } }
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
Expand All @@ -58,6 +55,14 @@ fun JournalStatusPickerBottomSheet(
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
) {

FilterChip(
leadingIcon = { StatusWithProgressIcon(null, null) },
selected = status == null,
onClick = { onStatusUpdated(null) },
label = { Text(stringResource(Res.string.status_no_status)) }
)

val statusSet = setOf(Status.DRAFT, Status.FINAL, Status.CANCELLED)

statusSet.forEach { selectableStatus ->
Expand Down Expand Up @@ -90,4 +95,20 @@ private fun JournalStatusPickerBottomSheet_Preview() {
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
private fun JournalStatusPickerBottomSheet_NO_STATUS_Preview() {
AppTheme(spectacledVariant = SpectacledVariant.JOURNALS) {
Scaffold {
JournalStatusPickerBottomSheet(
status = null,
sheetState = rememberBottomSheetState(initialValue = SheetValue.Expanded),
onStatusUpdated = {},
onDismiss = {}
)
}
}
}


Loading
Loading