diff --git a/app/src/googleplay/java/org/tasks/wear/WearDataService.kt b/app/src/googleplay/java/org/tasks/wear/WearDataService.kt index 8a2d3df87..20831d141 100644 --- a/app/src/googleplay/java/org/tasks/wear/WearDataService.kt +++ b/app/src/googleplay/java/org/tasks/wear/WearDataService.kt @@ -11,9 +11,14 @@ import com.todoroo.astrid.service.TaskCompleter import dagger.hilt.android.AndroidEntryPoint import org.tasks.GrpcProto.Settings import org.tasks.WearServiceGrpcKt +import org.tasks.analytics.Firebase +import org.tasks.billing.Inventory import org.tasks.extensions.wearDataLayerRegistry +import org.tasks.filters.FilterProvider +import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.Preferences import org.tasks.tasklist.HeaderFormatter +import org.tasks.themes.ColorProvider import javax.inject.Inject @OptIn(ExperimentalHorologistApi::class) @@ -24,6 +29,11 @@ class WearDataService : BaseGrpcDataService, private val firebase: Firebase, + private val filterProvider: FilterProvider, + private val inventory: Inventory, + private val colorProvider: ColorProvider, + private val defaultFilterProvider: DefaultFilterProvider, ) : WearServiceGrpcKt.WearServiceCoroutineImplBase() { override suspend fun getTasks(request: GetTasksRequest): Tasks { val position = request.position val limit = request.limit.takeIf { it > 0 } ?: Int.MAX_VALUE - val filter = MyTasksFilter.create() val settingsData = settings.data.firstOrNull() ?: GrpcProto.Settings.getDefaultInstance() + val filter = + defaultFilterProvider.getFilterFromPreference(settingsData.filter.takeIf { it.isNotBlank() }) val preferences = WearPreferences(appPreferences, settingsData) val collapsed = settingsData?.collapsedList?.toSet() ?: emptySet() val payload = SectionedDataSource( @@ -57,14 +74,14 @@ class WearService( is UiItem.Header -> GrpcProto.UiItem.newBuilder() .setId(item.value) - .setType(GrpcProto.UiItemType.Header) + .setType(ListItemType.Header) .setTitle(headerFormatter.headerString(item.value)) .setCollapsed(collapsed.contains(item.value)) .build() is UiItem.Task -> GrpcProto.UiItem.newBuilder() - .setType(GrpcProto.UiItemType.Task) + .setType(ListItemType.Item) .setId(item.task.id) .setPriority(item.task.priority) .setCompleted(item.task.isCompleted) @@ -80,7 +97,7 @@ class WearService( .setRepeating(item.task.task.isRecurring) .build() } - } + } ) .build() } @@ -101,7 +118,8 @@ class WearService( } else { if (collapsed.contains(request.value)) { collapsed.clear() - collapsed.addAll(it.collapsedList.toMutableList().apply { remove(request.value) }) + collapsed.addAll( + it.collapsedList.toMutableList().apply { remove(request.value) }) } } } @@ -118,4 +136,57 @@ class WearService( taskDao.setCollapsed(request.value, request.collapsed) return ToggleGroupResponse.newBuilder().build() } + + override suspend fun getLists(request: GrpcProto.GetListsRequest): GetListsResponse { + val position = request.position + val limit = request.limit.takeIf { it > 0 } ?: Int.MAX_VALUE + val selected = settings.data.firstOrNull()?.filter?.takeIf { it.isNotBlank() } + ?: defaultFilterProvider.getFilterPreferenceValue(MyTasksFilter.create()) + val filters = filterProvider.wearableFilters() + return GetListsResponse.newBuilder() + .setTotalItems(filters.size) + .addAllItems( + filters + .subList(position, (position + limit).coerceAtMost(filters.size)) + .map { item -> + when (item) { + is Filter -> { + ListItem.newBuilder() + .setId(defaultFilterProvider.getFilterPreferenceValue(item)) + .setType(ListItemType.Item) + .setTitle(item.title ?: "") + .setIcon(item.getIcon(inventory)) + .setColor(getColor(item)) + .setTaskCount(item.count.takeIf { it != NO_COUNT } ?: try { + taskDao.count(item) + } catch (e: Exception) { + Timber.e(e) + 0 + }) + .build() + } + + is NavigationDrawerSubheader -> + ListItem.newBuilder() + .setType(ListItemType.Header) + .setTitle(item.title ?: "") + .setId("${item.subheaderType}_${item.id}") + .build() + + else -> throw IllegalArgumentException() + } + } + ) + .build() + } + + private fun getColor(filter: Filter): Int { + if (filter.tint != 0) { + val color = colorProvider.getThemeColor(filter.tint, true) + if (color.isFree || inventory.purchasedThemes()) { + return color.primaryColor + } + } + return 0 + } } diff --git a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt index 1c4b28b9c..44e207724 100644 --- a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt +++ b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt @@ -8,6 +8,7 @@ package com.todoroo.astrid.dao import com.todoroo.astrid.timers.TimerPlugin import org.tasks.LocalBroadcastManager import org.tasks.data.TaskContainer +import org.tasks.data.count import org.tasks.data.dao.TaskDao import org.tasks.data.db.SuspendDbUtils.eachChunk import org.tasks.data.entity.Task @@ -40,6 +41,8 @@ class TaskDao @Inject constructor( suspend fun fetch(remoteId: String): Task? = taskDao.fetch(remoteId) + suspend fun count(item: Filter): Int = taskDao.count(item) + suspend fun getRecurringTasks(remoteIds: List): List = taskDao.getRecurringTasks(remoteIds) diff --git a/kmp/src/commonMain/kotlin/org/tasks/filters/FilterProvider.kt b/kmp/src/commonMain/kotlin/org/tasks/filters/FilterProvider.kt index 22dd6d1f0..bf7d68bd2 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/filters/FilterProvider.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/filters/FilterProvider.kt @@ -1,6 +1,7 @@ package org.tasks.filters import org.jetbrains.compose.resources.getString +import org.tasks.compose.drawer.DrawerConfiguration import org.tasks.data.GoogleTaskFilters import org.tasks.data.LocationFilters import org.tasks.data.NO_ORDER @@ -16,11 +17,10 @@ import org.tasks.data.entity.CaldavAccount.Companion.TYPE_LOCAL import org.tasks.data.entity.CaldavAccount.Companion.TYPE_OPENTASKS import org.tasks.data.setupLocalAccount import org.tasks.data.toGtasksFilter -import org.tasks.filters.NavigationDrawerSubheader.SubheaderType -import org.tasks.kmp.IS_DEBUG -import org.tasks.compose.drawer.DrawerConfiguration import org.tasks.data.toLocationFilter import org.tasks.data.toTagFilter +import org.tasks.filters.NavigationDrawerSubheader.SubheaderType +import org.tasks.kmp.IS_DEBUG import org.tasks.preferences.TasksPreferences import org.tasks.preferences.TasksPreferences.Companion.collapseDebug import org.tasks.preferences.TasksPreferences.Companion.collapseFilters @@ -59,6 +59,9 @@ class FilterProvider( suspend fun filterPickerItems(): List = getAllFilters(showCreate = false) + suspend fun wearableFilters(): List = + getAllFilters(showCreate = false, forceExpand = true, hideUnused = true) + suspend fun drawerCustomizationItems(): List = getAllFilters(showBuiltIn = false, showCreate = true) diff --git a/wear-datalayer/src/main/proto/grpc.proto b/wear-datalayer/src/main/proto/grpc.proto index 0dfcddd3d..a52fdd1c3 100644 --- a/wear-datalayer/src/main/proto/grpc.proto +++ b/wear-datalayer/src/main/proto/grpc.proto @@ -5,13 +5,13 @@ package org.tasks.grpc; option java_package = "org.tasks"; option java_outer_classname = "GrpcProto"; -enum UiItemType { +enum ListItemType { Header = 0; - Task = 1; + Item = 1; } message UiItem { - UiItemType type = 1; + ListItemType type = 1; uint64 id = 2; string title = 3; bool completed = 4; @@ -34,7 +34,7 @@ message LastUpdate { message Settings { repeated uint64 collapsed = 1; - string filter = 2; + optional string filter = 2; bool showHidden = 3; bool showCompleted = 4; } @@ -59,10 +59,29 @@ message ToggleGroupRequest { } message ToggleGroupResponse {} +message ListItem { + ListItemType type = 1; + string id = 2; + string title = 3; + string icon = 4; + uint32 color = 5; + uint32 taskCount = 6; +} + +message GetListsRequest { + uint32 position = 1; + uint32 limit = 2; +} +message GetListsResponse { + uint32 totalItems = 1; + repeated ListItem items = 2; +} + service WearService { rpc getTasks(GetTasksRequest) returns (Tasks); rpc completeTask(CompleteTaskRequest) returns (CompleteTaskResponse); rpc toggleGroup(ToggleGroupRequest) returns (ToggleGroupResponse); rpc updateSettings(UpdateSettingsRequest) returns (Settings); rpc toggleSubtasks(ToggleGroupRequest) returns (ToggleGroupResponse); + rpc getLists(GetListsRequest) returns (GetListsResponse); } diff --git a/wear/src/main/java/org/tasks/presentation/MainActivity.kt b/wear/src/main/java/org/tasks/presentation/MainActivity.kt index 804384611..092d97615 100644 --- a/wear/src/main/java/org/tasks/presentation/MainActivity.kt +++ b/wear/src/main/java/org/tasks/presentation/MainActivity.kt @@ -31,6 +31,8 @@ import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.tooling.preview.devices.WearDevices import com.google.android.horologist.compose.layout.AppScaffold +import org.tasks.presentation.screens.MenuScreen +import org.tasks.presentation.screens.MenuViewModel import org.tasks.presentation.screens.SettingsScreen import org.tasks.presentation.screens.SettingsViewModel import org.tasks.presentation.screens.TaskListScreen @@ -54,6 +56,7 @@ class MainActivity : ComponentActivity() { val navController = rememberSwipeDismissableNavController() val taskListViewModel: TaskListViewModel = viewModel() val taskListItems = taskListViewModel.uiItems.collectAsLazyPagingItems() + val settingsViewModel: SettingsViewModel = viewModel() SwipeDismissableNavHost( startDestination = "task_list", navController = navController, @@ -87,14 +90,21 @@ class MainActivity : ComponentActivity() { } composable( route = "menu", - ) { - + ) { navBackStackEntry -> + val menuViewModel: MenuViewModel = viewModel(navBackStackEntry) + MenuScreen( + items = menuViewModel.uiItems.collectAsLazyPagingItems(), + selectFilter = { + settingsViewModel.setFilter(it.id) + navController.popBackStack() + }, + ) } composable( route = "settings", - ) { navBackStackEntry -> - val settingsViewModel: SettingsViewModel = viewModel(navBackStackEntry) - val viewState = settingsViewModel.viewState.collectAsStateWithLifecycle().value + ) { + val viewState = + settingsViewModel.viewState.collectAsStateWithLifecycle().value if (viewState.initialized) { SettingsScreen( showHidden = viewState.settings.showHidden, diff --git a/wear/src/main/java/org/tasks/presentation/components/Card.kt b/wear/src/main/java/org/tasks/presentation/components/Card.kt new file mode 100644 index 000000000..3bd629704 --- /dev/null +++ b/wear/src/main/java/org/tasks/presentation/components/Card.kt @@ -0,0 +1,33 @@ +package org.tasks.presentation.components + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.Card +import androidx.wear.compose.material.MaterialTheme + +@Composable +fun Card( + backgroundColor: Color = MaterialTheme.colors.surface, + icon: @Composable () -> Unit = {}, + onClick: () -> Unit, + content: @Composable RowScope.() -> Unit, +) { + Card( + onClick = onClick, + backgroundPainter = ColorPainter(backgroundColor), + contentPadding = PaddingValues(0.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + icon() + content() + } + } +} diff --git a/wear/src/main/java/org/tasks/presentation/components/CollapsibleHeader.kt b/wear/src/main/java/org/tasks/presentation/components/CollapsibleHeader.kt new file mode 100644 index 000000000..4eb780888 --- /dev/null +++ b/wear/src/main/java/org/tasks/presentation/components/CollapsibleHeader.kt @@ -0,0 +1,25 @@ +package org.tasks.presentation.components + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.Text + +@Composable +fun CollapsibleHeader( + title: String, + collapsed: Boolean, + onClick: () -> Unit, +) { + Header( + onClick = onClick, + ) { + Text( + text = title, + ) + Spacer(modifier = Modifier.width(4.dp)) + Chevron(collapsed = collapsed) + } +} diff --git a/wear/src/main/java/org/tasks/presentation/components/EmptyCard.kt b/wear/src/main/java/org/tasks/presentation/components/EmptyCard.kt new file mode 100644 index 000000000..62d2653db --- /dev/null +++ b/wear/src/main/java/org/tasks/presentation/components/EmptyCard.kt @@ -0,0 +1,6 @@ +package org.tasks.presentation.components + +import androidx.compose.runtime.Composable + +@Composable +fun EmptyCard() = Card(onClick = {}) { } diff --git a/wear/src/main/java/org/tasks/presentation/components/GroupSeparator.kt b/wear/src/main/java/org/tasks/presentation/components/Header.kt similarity index 70% rename from wear/src/main/java/org/tasks/presentation/components/GroupSeparator.kt rename to wear/src/main/java/org/tasks/presentation/components/Header.kt index c0cd7bc25..6cd57edcd 100644 --- a/wear/src/main/java/org/tasks/presentation/components/GroupSeparator.kt +++ b/wear/src/main/java/org/tasks/presentation/components/Header.kt @@ -3,10 +3,8 @@ package org.tasks.presentation.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -16,10 +14,17 @@ import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.Text @Composable -fun GroupSeparator( - title: String, - collapsed: Boolean, - onClick: () -> Unit, +fun Header(text: String) { + Header(clickable = false) { + Text(text) + } +} + +@Composable +internal fun Header( + clickable: Boolean = true, + onClick: () -> Unit = {}, + content: @Composable () -> Unit, ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -27,13 +32,9 @@ fun GroupSeparator( modifier = Modifier .clip(MaterialTheme.shapes.large) .fillMaxWidth() - .clickable(onClick = onClick) + .clickable(enabled = clickable, onClick = onClick) .padding(12.dp) ) { - Text( - text = title, - ) - Spacer(modifier = Modifier.width(4.dp)) - Chevron(collapsed = collapsed) + content() } -} +} \ No newline at end of file diff --git a/wear/src/main/java/org/tasks/presentation/screens/MenuScreen.kt b/wear/src/main/java/org/tasks/presentation/screens/MenuScreen.kt new file mode 100644 index 000000000..4e23c61a5 --- /dev/null +++ b/wear/src/main/java/org/tasks/presentation/screens/MenuScreen.kt @@ -0,0 +1,97 @@ +package org.tasks.presentation.screens + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.paging.compose.LazyPagingItems +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumn +import com.google.android.horologist.compose.layout.ScreenScaffold +import com.google.android.horologist.compose.layout.rememberResponsiveColumnState +import com.google.android.horologist.compose.paging.items +import org.tasks.GrpcProto.ListItem +import org.tasks.GrpcProto.ListItemType +import org.tasks.compose.components.imageVectorByName +import org.tasks.presentation.components.Card +import org.tasks.presentation.components.EmptyCard +import org.tasks.presentation.components.Header + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun MenuScreen( + items: LazyPagingItems, + selectFilter: (ListItem) -> Unit, +) { + val columnState = rememberResponsiveColumnState() + ScreenScaffold( + scrollState = columnState, + ) { + ScalingLazyColumn( + modifier = Modifier.fillMaxSize(), + columnState = columnState, + ) { + items( + items = items, + key = { item -> "${item.type}_${item.id}" }, + ) { item -> + if (item == null) { + EmptyCard() + } else { + when (item.type) { + ListItemType.Header -> + Header(text = item.title) + + ListItemType.Item -> { + Card( + icon = { + val icon = imageVectorByName(item.icon) + Box( + modifier = Modifier.size(48.dp), + contentAlignment = Alignment.Center, + ) { + if (icon != null) { + Icon( + imageVector = icon, + contentDescription = null, + tint = when (item.color) { + 0 -> MaterialTheme.colors.onSurface + else -> Color(color = item.color) + }, + ) + } + } + }, + onClick = { selectFilter(item) }, + ) { + Text( + item.title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(end = 12.dp).weight(1f), + ) + if (item.taskCount > 0) { + Text( + item.taskCount.toString(), // TODO: localize + modifier = Modifier.padding(end = 16.dp) + ) + } + } + } + + else -> throw IllegalArgumentException() + } + } + } + } + } +} \ No newline at end of file diff --git a/wear/src/main/java/org/tasks/presentation/screens/MenuViewModel.kt b/wear/src/main/java/org/tasks/presentation/screens/MenuViewModel.kt new file mode 100644 index 000000000..77a8af05f --- /dev/null +++ b/wear/src/main/java/org/tasks/presentation/screens/MenuViewModel.kt @@ -0,0 +1,74 @@ +package org.tasks.presentation.screens + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.data.ProtoDataStoreHelper.protoFlow +import com.google.android.horologist.data.TargetNodeId +import com.google.android.horologist.datalayer.grpc.GrpcExtensions.grpcClient +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.tasks.GrpcProto +import org.tasks.GrpcProto.LastUpdate +import org.tasks.GrpcProto.ListItem +import org.tasks.GrpcProto.Settings +import org.tasks.WearServiceGrpcKt +import org.tasks.extensions.wearDataLayerRegistry +import org.tasks.presentation.MyPagingSource + +@OptIn(ExperimentalHorologistApi::class) +class MenuViewModel( + application: Application +): AndroidViewModel(application) { + private var pagingSource: MyPagingSource? = null + val uiItems: Flow> = Pager( + config = PagingConfig(pageSize = 20), + pagingSourceFactory = { + MyPagingSource { position, limit -> + wearService + .getLists( + GrpcProto + .GetListsRequest + .newBuilder() + .setPosition(position) + .setLimit(limit) + .build() + ) + .let { Pair(it.totalItems, it.itemsList) } + } + .also { pagingSource = it } + } + ) + .flow + .cachedIn(viewModelScope) + + private val registry = application.wearDataLayerRegistry(viewModelScope) + + private val wearService : WearServiceGrpcKt.WearServiceCoroutineStub = registry.grpcClient( + nodeId = TargetNodeId.PairedPhone, + coroutineScope = viewModelScope, + ) { + WearServiceGrpcKt.WearServiceCoroutineStub(it) + } + + init { + registry + .protoFlow(TargetNodeId.PairedPhone) + .onEach { invalidate() } + .launchIn(viewModelScope) + registry + .protoFlow(TargetNodeId.PairedPhone) + .onEach { invalidate() } + .launchIn(viewModelScope) + } + + private fun invalidate() { + pagingSource?.invalidate() + } +} \ No newline at end of file diff --git a/wear/src/main/java/org/tasks/presentation/screens/SettingsViewModel.kt b/wear/src/main/java/org/tasks/presentation/screens/SettingsViewModel.kt index 7889368ab..b07e32646 100644 --- a/wear/src/main/java/org/tasks/presentation/screens/SettingsViewModel.kt +++ b/wear/src/main/java/org/tasks/presentation/screens/SettingsViewModel.kt @@ -19,6 +19,7 @@ import org.tasks.SettingsKt import org.tasks.WearServiceGrpcKt import org.tasks.copy import org.tasks.extensions.wearDataLayerRegistry +import org.tasks.tasklist.SectionedDataSource.Companion.HEADER_COMPLETED data class ViewState( val initialized: Boolean = false, @@ -65,6 +66,14 @@ class SettingsViewModel( } } + fun setFilter(filter: String) { + updateSettings { + this.filter = filter + collapsed.clear() + collapsed.add(HEADER_COMPLETED) + } + } + private fun updateSettings(block: SettingsKt.Dsl.() -> Unit) = viewModelScope.launch { wearService.updateSettings( GrpcProto.UpdateSettingsRequest.newBuilder() 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 63d515f1c..4658a8f40 100644 --- a/wear/src/main/java/org/tasks/presentation/screens/TaskListScreen.kt +++ b/wear/src/main/java/org/tasks/presentation/screens/TaskListScreen.kt @@ -33,7 +33,8 @@ import com.google.android.horologist.compose.paging.items import org.jetbrains.compose.resources.stringResource import org.tasks.GrpcProto import org.tasks.kmp.org.tasks.themes.ColorProvider -import org.tasks.presentation.components.GroupSeparator +import org.tasks.presentation.components.CollapsibleHeader +import org.tasks.presentation.components.EmptyCard import org.tasks.presentation.components.TaskCard import tasks.kmp.generated.resources.Res import tasks.kmp.generated.resources.add_task @@ -75,14 +76,10 @@ fun TaskListScreen( key = { item -> "${item.type}_${item.id}" }, ) { item -> if (item == null) { - TaskCard( - text = "", - icon = {}, - onClick = {}, - ) + EmptyCard() } else { when (item.type) { - GrpcProto.UiItemType.Task -> + GrpcProto.ListItemType.Item -> Row { if (item.indent > 0) { Spacer(modifier = Modifier.width(20.dp * item.indent)) @@ -119,8 +116,8 @@ fun TaskListScreen( ) } - GrpcProto.UiItemType.Header -> - GroupSeparator( + GrpcProto.ListItemType.Header -> + CollapsibleHeader( title = item.title, collapsed = item.collapsed, onClick = { toggleGroup(item.id, !item.collapsed) }, diff --git a/wear/src/main/java/org/tasks/presentation/screens/TaskListViewModel.kt b/wear/src/main/java/org/tasks/presentation/screens/TaskListViewModel.kt index ebf6e20e8..58ae6e5fb 100644 --- a/wear/src/main/java/org/tasks/presentation/screens/TaskListViewModel.kt +++ b/wear/src/main/java/org/tasks/presentation/screens/TaskListViewModel.kt @@ -63,11 +63,11 @@ class TaskListViewModel( init { registry .protoFlow(TargetNodeId.PairedPhone) - .onEach { pagingSource?.invalidate() } + .onEach { invalidate() } .launchIn(viewModelScope) registry .protoFlow(TargetNodeId.PairedPhone) - .onEach { pagingSource?.invalidate() } + .onEach { invalidate() } .launchIn(viewModelScope) } @@ -78,17 +78,24 @@ class TaskListViewModel( .setCollapsed(setCollapsed) .build() ) + invalidate() } fun completeTask(id: Long, completed: Boolean) = viewModelScope.launch { wearService.completeTask( CompleteTaskRequest.newBuilder().setId(id).setCompleted(completed).build() ) + invalidate() } fun toggleSubtasks(id: Long, collapsed: Boolean) = viewModelScope.launch { wearService.toggleSubtasks( ToggleGroupRequest.newBuilder().setValue(id).setCollapsed(collapsed).build() ) + invalidate() + } + + private fun invalidate() { + pagingSource?.invalidate() } }