diff --git a/app/src/googleplay/java/org/tasks/wear/WearDataService.kt b/app/src/googleplay/java/org/tasks/wear/WearDataService.kt index 8011b20f7..69dbebc4c 100644 --- a/app/src/googleplay/java/org/tasks/wear/WearDataService.kt +++ b/app/src/googleplay/java/org/tasks/wear/WearDataService.kt @@ -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 + 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 = 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() - } - } } diff --git a/app/src/main/java/org/tasks/tasklist/HeaderFormatter.kt b/app/src/main/java/org/tasks/tasklist/HeaderFormatter.kt index 8f35b5d4f..349c04ac1 100644 --- a/app/src/main/java/org/tasks/tasklist/HeaderFormatter.kt +++ b/app/src/main/java/org/tasks/tasklist/HeaderFormatter.kt @@ -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, diff --git a/wear-datalayer/src/main/proto/grpc.proto b/wear-datalayer/src/main/proto/grpc.proto index 1a4be8ce6..5cafc7e30 100644 --- a/wear-datalayer/src/main/proto/grpc.proto +++ b/wear-datalayer/src/main/proto/grpc.proto @@ -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); -} \ No newline at end of file +} diff --git a/wear/src/main/java/org/tasks/presentation/MainActivity.kt b/wear/src/main/java/org/tasks/presentation/MainActivity.kt index 65b7193e6..ba4898a22 100644 --- a/wear/src/main/java/org/tasks/presentation/MainActivity.kt +++ b/wear/src/main/java/org/tasks/presentation/MainActivity.kt @@ -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") }, ) diff --git a/wear/src/main/java/org/tasks/presentation/screens/TaskListScreen.kt b/wear/src/main/java/org/tasks/presentation/screens/TaskListScreen.kt index 6f2b8fef5..74fc0c1a8 100644 --- a/wear/src/main/java/org/tasks/presentation/screens/TaskListScreen.kt +++ b/wear/src/main/java/org/tasks/presentation/screens/TaskListScreen.kt @@ -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, + uiItems: List, 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, )