Group separators on Android Wear

pull/3024/merge
Alex Baker 1 day ago
parent d5f9c24da4
commit d2ff0519be

@ -9,6 +9,7 @@ import com.todoroo.astrid.service.TaskCompleter
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.WearServiceGrpcKt
import org.tasks.preferences.Preferences
import org.tasks.tasklist.HeaderFormatter
import javax.inject.Inject
@OptIn(ExperimentalHorologistApi::class)
@ -18,6 +19,7 @@ class WearDataService : BaseGrpcDataService<WearServiceGrpcKt.WearServiceCorouti
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences
@Inject lateinit var taskCompleter: TaskCompleter
@Inject lateinit var headerFormatter: HeaderFormatter
override val registry: WearDataLayerRegistry by lazy {
WearDataLayerRegistry.fromContext(
@ -33,6 +35,7 @@ class WearDataService : BaseGrpcDataService<WearServiceGrpcKt.WearServiceCorouti
taskDao = taskDao,
preferences = preferences,
taskCompleter = taskCompleter,
headerFormatter = headerFormatter,
)
}
}

@ -2,26 +2,61 @@ package org.tasks.wear
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.service.TaskCompleter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.tasks.GrpcProto
import org.tasks.GrpcProto.CompleteTaskRequest
import org.tasks.GrpcProto.CompleteTaskResponse
import org.tasks.GrpcProto.GetTasksRequest
import org.tasks.GrpcProto.Task
import org.tasks.GrpcProto.Tasks
import org.tasks.WearServiceGrpcKt
import org.tasks.filters.AstridOrderingFilter
import org.tasks.filters.MyTasksFilter
import org.tasks.preferences.Preferences
import org.tasks.tasklist.HeaderFormatter
import org.tasks.tasklist.SectionedDataSource
import org.tasks.tasklist.UiItem
class WearService(
private val taskDao: TaskDao,
private val preferences: Preferences,
private val taskCompleter: TaskCompleter,
private val headerFormatter: HeaderFormatter,
) : WearServiceGrpcKt.WearServiceCoroutineImplBase() {
override suspend fun getTasks(request: GetTasksRequest): Tasks {
val filter = MyTasksFilter.create()
val payload = SectionedDataSource(
tasks = taskDao.fetchTasks(preferences, filter),
disableHeaders = filter.disableHeaders()
|| (filter.supportsManualSort() && preferences.isManualSort)
|| (filter is AstridOrderingFilter && preferences.isAstridSort),
groupMode = preferences.groupMode,
subtaskMode = preferences.subtaskMode,
completedAtBottom = preferences.completedTasksAtBottom,
)
return Tasks.newBuilder()
.addAllTasks(getTasks())
.addAllItems(
payload.map { item ->
when (item) {
is UiItem.Header ->
GrpcProto.UiItem.newBuilder()
.setType(GrpcProto.UiItemType.Header)
.setTitle(headerFormatter.headerString(item.value))
.build()
is UiItem.Task ->
GrpcProto.UiItem.newBuilder()
.setType(GrpcProto.UiItemType.Task)
.setId(item.task.id)
.setPriority(item.task.priority)
.setCompleted(item.task.isCompleted)
.apply {
if (item.task.title != null) {
setTitle(item.task.title)
}
}
.setRepeating(item.task.task.isRecurring)
.build()
}
}
)
.build()
}
@ -29,21 +64,4 @@ class WearService(
taskCompleter.setComplete(request.id, request.completed)
return CompleteTaskResponse.newBuilder().setSuccess(true).build()
}
private suspend fun getTasks(): List<Task> = withContext(Dispatchers.IO) {
val tasks = taskDao.fetchTasks(preferences, MyTasksFilter.create())
return@withContext tasks.map {
Task.newBuilder()
.setId(it.task.id)
.setPriority(it.task.priority)
.setCompleted(it.task.isCompleted)
.apply {
if (it.task.title != null) {
setTitle(it.task.title)
}
}
.setRepeating(it.task.isRecurring)
.build()
}
}
}

@ -29,7 +29,7 @@ class HeaderFormatter @Inject constructor(
headerString(value, groupMode, alwaysDisplayFullDate, style, compact)
}
private suspend fun headerString(
suspend fun headerString(
value: Long,
groupMode: Int = preferences.groupMode,
alwaysDisplayFullDate: Boolean = preferences.alwaysDisplayFullDate,

@ -5,16 +5,22 @@ package org.tasks.grpc;
option java_package = "org.tasks";
option java_outer_classname = "GrpcProto";
message Task {
uint64 id = 1;
string title = 2;
bool completed = 3;
uint32 priority = 4;
bool repeating = 5;
enum UiItemType {
Header = 0;
Task = 1;
}
message UiItem {
UiItemType type = 1;
uint64 id = 2;
string title = 3;
bool completed = 4;
uint32 priority = 5;
bool repeating = 6;
}
message Tasks {
repeated Task tasks = 1;
repeated UiItem items = 1;
}
message LastUpdate {
@ -31,4 +37,4 @@ message CompleteTaskResponse { bool success = 1; }
service WearService {
rpc getTasks(GetTasksRequest) returns (Tasks);
rpc completeTask(CompleteTaskRequest) returns (CompleteTaskResponse);
}
}

@ -59,7 +59,7 @@ class MainActivity : ComponentActivity() {
val viewModel: TaskListViewModel = viewModel(navBackStackEntry)
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
TaskListScreen(
tasks = uiState.tasks.tasksList,
uiItems = uiState.tasks.itemsList,
onComplete = { viewModel.completeTask(it) },
onClick = { navController.navigate("task_edit/$it") },
)

@ -3,6 +3,7 @@ package org.tasks.presentation.screens
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CheckBox
@ -14,6 +15,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Button
import androidx.wear.compose.material.ButtonDefaults
@ -31,7 +33,7 @@ import org.tasks.kmp.org.tasks.themes.ColorProvider
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun TaskListScreen(
tasks: MutableList<GrpcProto.Task>,
uiItems: List<GrpcProto.UiItem>,
onComplete: (Long) -> Unit,
onClick: (Long) -> Unit,
) {
@ -41,23 +43,46 @@ fun TaskListScreen(
modifier = Modifier.fillMaxSize(),
columnState = columnState,
) {
items(tasks.size) { index ->
val task = tasks[index]
key(task.id) {
TaskCard(
task = task,
onComplete = { onComplete(task.id) },
onClick = { onClick(task.id) },
)
items(uiItems.size) { index ->
val item = uiItems[index]
key(item.id) {
when (item.type) {
GrpcProto.UiItemType.Task ->
TaskCard(
task = item,
onComplete = { onComplete(item.id) },
onClick = { onClick(item.id) },
)
GrpcProto.UiItemType.Header ->
GroupSeparator(header = item)
else -> {
throw IllegalStateException("Unknown item type: ${item.type}")
}
}
}
}
}
}
}
@Composable
fun GroupSeparator(
header: GrpcProto.UiItem,
) {
Text(
text = header.title,
modifier = Modifier
.padding(vertical = 12.dp)
.fillMaxWidth(),
textAlign = TextAlign.Center,
)
}
@Composable
fun TaskCard(
task: GrpcProto.Task,
task: GrpcProto.UiItem,
onComplete: () -> Unit,
onClick: () -> Unit,
) {
@ -80,7 +105,11 @@ fun TaskCard(
else -> Icons.Outlined.CheckBoxOutlineBlank
},
tint = Color(
ColorProvider.priorityColor(task.priority, isDarkMode = true, desaturate = true)
ColorProvider.priorityColor(
task.priority,
isDarkMode = true,
desaturate = true
)
),
contentDescription = null,
)

Loading…
Cancel
Save