Show task timestamp on WearOS

pull/2983/head
Alex Baker 1 year ago
parent d4aeb34b87
commit 675b43ff95

@ -1,5 +1,6 @@
package org.tasks.wear package org.tasks.wear
import android.text.format.DateFormat
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.annotations.ExperimentalHorologistApi
@ -58,6 +59,7 @@ class WearDataService : BaseGrpcDataService<WearServiceGrpcKt.WearServiceCorouti
colorProvider = colorProvider, colorProvider = colorProvider,
defaultFilterProvider = defaultFilterProvider, defaultFilterProvider = defaultFilterProvider,
taskCreator = taskCreator, taskCreator = taskCreator,
is24HourTime = DateFormat.is24HourFormat(applicationContext),
) )
} }
} }

@ -1,6 +1,7 @@
package org.tasks.wear package org.tasks.wear
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import com.todoroo.astrid.core.SortHelper.SORT_DUE
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.service.TaskCompleter import com.todoroo.astrid.service.TaskCompleter
import com.todoroo.astrid.service.TaskCreator import com.todoroo.astrid.service.TaskCreator
@ -30,12 +31,16 @@ import org.tasks.filters.MyTasksFilter
import org.tasks.filters.NavigationDrawerSubheader import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.filters.getIcon import org.tasks.filters.getIcon
import org.tasks.kmp.org.tasks.time.DateStyle import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.tasklist.HeaderFormatter import org.tasks.tasklist.HeaderFormatter
import org.tasks.tasklist.SectionedDataSource import org.tasks.tasklist.SectionedDataSource
import org.tasks.tasklist.UiItem import org.tasks.tasklist.UiItem
import org.tasks.themes.ColorProvider import org.tasks.themes.ColorProvider
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.startOfDay
import timber.log.Timber import timber.log.Timber
class WearService( class WearService(
@ -50,6 +55,7 @@ class WearService(
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val defaultFilterProvider: DefaultFilterProvider, private val defaultFilterProvider: DefaultFilterProvider,
private val taskCreator: TaskCreator, private val taskCreator: TaskCreator,
private val is24HourTime: Boolean,
) : WearServiceGrpcKt.WearServiceCoroutineImplBase() { ) : WearServiceGrpcKt.WearServiceCoroutineImplBase() {
override suspend fun getTasks(request: GetTasksRequest): Tasks { override suspend fun getTasks(request: GetTasksRequest): Tasks {
val position = request.position val position = request.position
@ -84,7 +90,23 @@ class WearService(
.setCollapsed(collapsed.contains(item.value)) .setCollapsed(collapsed.contains(item.value))
.build() .build()
is UiItem.Task -> is UiItem.Task -> {
val timestamp = if (preferences.groupMode == SORT_DUE &&
(item.task.sortGroup
?: 0) >= currentTimeMillis().startOfDay()
) {
item.task.takeIf { it.hasDueTime() }?.let {
getTimeString(item.task.dueDate, is24HourTime)
}
} else if (item.task.hasDueDate()) {
getRelativeDateTime(
item.task.dueDate,
is24HourTime,
)
} else {
null
}
GrpcProto.UiItem.newBuilder() GrpcProto.UiItem.newBuilder()
.setType(ListItemType.Item) .setType(ListItemType.Item)
.setId(item.task.id) .setId(item.task.id)
@ -98,11 +120,15 @@ class WearService(
if (item.task.title != null) { if (item.task.title != null) {
setTitle(item.task.title) setTitle(item.task.title)
} }
if (timestamp != null) {
setTimestamp(timestamp)
}
} }
.setRepeating(item.task.task.isRecurring) .setRepeating(item.task.task.isRecurring)
.build() .build()
} }
} }
}
) )
.build() .build()
} }

@ -21,6 +21,7 @@ message UiItem {
bool hidden = 8; bool hidden = 8;
uint32 indent = 9; uint32 indent = 9;
uint32 numSubtasks = 10; uint32 numSubtasks = 10;
optional string timestamp = 11;
} }
message Tasks { message Tasks {

@ -1,9 +1,14 @@
package org.tasks.presentation.components package org.tasks.presentation.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -21,6 +26,7 @@ import androidx.wear.compose.material.Text
@Composable @Composable
fun TaskCard( fun TaskCard(
text: String, text: String,
timestamp: String?,
hidden: Boolean = false, hidden: Boolean = false,
numSubtasks: Int = 0, numSubtasks: Int = 0,
subtasksCollapsed: Boolean = false, subtasksCollapsed: Boolean = false,
@ -44,13 +50,21 @@ fun TaskCard(
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
color = contentColor, color = contentColor,
modifier = Modifier.alpha(if (hidden) .6f else 1f).weight(1f), modifier = Modifier
.alpha(if (hidden) .6f else 1f)
.weight(1f),
) )
if (numSubtasks > 0) { if (numSubtasks > 0) {
Button( Button(
onClick = toggleSubtasks, onClick = toggleSubtasks,
colors = ButtonDefaults.outlinedButtonColors() colors = ButtonDefaults.outlinedButtonColors(),
shape = if (timestamp.isNullOrBlank()) CircleShape else RoundedCornerShape(16.dp),
modifier = Modifier.wrapContentWidth(),
) { ) {
Column(
horizontalAlignment = Alignment.End,
) {
Timestamp(timestamp, contentColor)
Row( Row(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@ -61,9 +75,31 @@ fun TaskCard(
Chevron(subtasksCollapsed) Chevron(subtasksCollapsed)
} }
} }
}
} else { } else {
Timestamp(timestamp, contentColor)
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
} }
} }
} }
} }
@Composable
private fun Timestamp(
timestamp: String?,
color: Color,
) {
if (timestamp.isNullOrBlank()) {
return
}
Text(
text = timestamp,
color = color,
style = MaterialTheme.typography.caption2,
modifier = Modifier
.alpha(.6f)
.padding(start = 4.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}

@ -58,7 +58,7 @@ fun TaskEditScreen(
keyboardInputRequest.openKeyboard() keyboardInputRequest.openKeyboard()
} }
} }
if (uiState.taskId == 0L) { if (!uiState.loaded) {
Box( Box(
modifier = Modifier.fillMaxRectangle(), modifier = Modifier.fillMaxRectangle(),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,

@ -17,6 +17,7 @@ import org.tasks.extensions.wearDataLayerRegistry
import timber.log.Timber import timber.log.Timber
data class UiState( data class UiState(
val loaded: Boolean = false,
val taskId: Long = 0, val taskId: Long = 0,
val completed: Boolean = false, val completed: Boolean = false,
val repeating: Boolean = false, val repeating: Boolean = false,
@ -27,9 +28,9 @@ data class UiState(
@OptIn(ExperimentalHorologistApi::class) @OptIn(ExperimentalHorologistApi::class)
class TaskEditViewModel( class TaskEditViewModel(
applicationContext: Context, applicationContext: Context,
private val taskId: Long, taskId: Long,
) : ViewModel() { ) : ViewModel() {
private val _uiState = MutableStateFlow(UiState()) private val _uiState = MutableStateFlow(UiState(taskId = taskId))
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
private val registry = applicationContext.wearDataLayerRegistry(viewModelScope) private val registry = applicationContext.wearDataLayerRegistry(viewModelScope)
@ -54,6 +55,7 @@ class TaskEditViewModel(
Timber.d("Received $task") Timber.d("Received $task")
_uiState.update { _uiState.update {
it.copy( it.copy(
loaded = true,
taskId = taskId, taskId = taskId,
completed = task.completed, completed = task.completed,
title = task.title, title = task.title,
@ -77,7 +79,7 @@ class TaskEditViewModel(
wearService.saveTask( wearService.saveTask(
GrpcProto.SaveTaskRequest.newBuilder() GrpcProto.SaveTaskRequest.newBuilder()
.setTitle(state.title) .setTitle(state.title)
.setTaskId(taskId) .setTaskId(state.taskId)
.setCompleted(state.completed) .setCompleted(state.completed)
.build() .build()
) )

@ -112,6 +112,7 @@ fun TaskListScreen(
} }
TaskCard( TaskCard(
text = item.title, text = item.title,
timestamp = item.timestamp,
hidden = item.hidden, hidden = item.hidden,
subtasksCollapsed = item.collapsed, subtasksCollapsed = item.collapsed,
numSubtasks = item.numSubtasks, numSubtasks = item.numSubtasks,

Loading…
Cancel
Save