diff --git a/app/src/googleplay/java/org/tasks/wear/WearDataService.kt b/app/src/googleplay/java/org/tasks/wear/WearDataService.kt index f2a73316a..8a2d3df87 100644 --- a/app/src/googleplay/java/org/tasks/wear/WearDataService.kt +++ b/app/src/googleplay/java/org/tasks/wear/WearDataService.kt @@ -36,7 +36,7 @@ class WearDataService : BaseGrpcDataService, @@ -32,7 +32,8 @@ class WearService( val position = request.position val limit = request.limit.takeIf { it > 0 } ?: Int.MAX_VALUE val filter = MyTasksFilter.create() - val settingsData = settings.data.firstOrNull() + val settingsData = settings.data.firstOrNull() ?: GrpcProto.Settings.getDefaultInstance() + val preferences = WearPreferences(appPreferences, settingsData) val collapsed = settingsData?.collapsedList?.toSet() ?: emptySet() val payload = SectionedDataSource( tasks = taskDao.fetchTasks(preferences, filter), @@ -102,4 +103,8 @@ class WearService( return ToggleGroupResponse.getDefaultInstance() } + + override suspend fun updateSettings(request: GrpcProto.UpdateSettingsRequest): GrpcProto.Settings { + return settings.updateData { request.settings } + } } 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 17e74bf4c..1c4b28b9c 100644 --- a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt +++ b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt @@ -20,6 +20,7 @@ import org.tasks.jobs.WorkManager import org.tasks.location.GeofenceApi import org.tasks.notifications.NotificationManager import org.tasks.preferences.Preferences +import org.tasks.preferences.QueryPreferences import org.tasks.sync.SyncAdapters import javax.inject.Inject @@ -54,7 +55,7 @@ class TaskDao @Inject constructor( suspend fun getCaldavTasksToPush(calendar: String): List = taskDao.getCaldavTasksToPush(calendar) - suspend fun fetchTasks(preferences: Preferences, filter: Filter): List = + suspend fun fetchTasks(preferences: QueryPreferences, filter: Filter): List = taskDao.fetchTasks(preferences, filter) suspend fun touch(id: Long) = touch(listOf(id)) diff --git a/wear-datalayer/src/main/proto/grpc.proto b/wear-datalayer/src/main/proto/grpc.proto index 8da8417de..fd174f4f2 100644 --- a/wear-datalayer/src/main/proto/grpc.proto +++ b/wear-datalayer/src/main/proto/grpc.proto @@ -33,6 +33,11 @@ message LastUpdate { message Settings { repeated uint64 collapsed = 1; string filter = 2; + bool showHidden = 3; +} + +message UpdateSettingsRequest { + Settings settings = 1; } message GetTasksRequest { @@ -55,4 +60,5 @@ service WearService { rpc getTasks(GetTasksRequest) returns (Tasks); rpc completeTask(CompleteTaskRequest) returns (CompleteTaskResponse); rpc toggleGroup(ToggleGroupRequest) returns (ToggleGroupResponse); + rpc updateSettings(UpdateSettingsRequest) returns (Settings); } diff --git a/wear/src/main/java/org/tasks/presentation/MainActivity.kt b/wear/src/main/java/org/tasks/presentation/MainActivity.kt index 184f0349e..bb3cfc788 100644 --- a/wear/src/main/java/org/tasks/presentation/MainActivity.kt +++ b/wear/src/main/java/org/tasks/presentation/MainActivity.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavType import androidx.navigation.navArgument @@ -32,6 +33,8 @@ import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.tooling.preview.devices.WearDevices import com.google.android.horologist.compose.layout.AppScaffold import org.tasks.R +import org.tasks.presentation.screens.SettingsScreen +import org.tasks.presentation.screens.SettingsViewModel import org.tasks.presentation.screens.TaskListScreen import org.tasks.presentation.screens.TaskListViewModel import org.tasks.presentation.theme.TasksTheme @@ -88,8 +91,17 @@ class MainActivity : ComponentActivity() { } composable( route = "settings", - ) { - + ) { navBackStackEntry -> + val settingsViewModel: SettingsViewModel = viewModel(navBackStackEntry) + val viewState = settingsViewModel.viewState.collectAsStateWithLifecycle().value + if (viewState.initialized) { + SettingsScreen( + showHidden = viewState.settings.showHidden, + toggleShowHidden = { settingsViewModel.setShowHidden(it) }, + ) + } else { + // TODO: show spinner + } } } } diff --git a/wear/src/main/java/org/tasks/presentation/screens/SettingsScreen.kt b/wear/src/main/java/org/tasks/presentation/screens/SettingsScreen.kt new file mode 100644 index 000000000..8a1c006d6 --- /dev/null +++ b/wear/src/main/java/org/tasks/presentation/screens/SettingsScreen.kt @@ -0,0 +1,38 @@ +package org.tasks.presentation.screens + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +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.material.ToggleChip +import com.google.android.horologist.compose.material.ToggleChipToggleControl + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun SettingsScreen( + + showHidden: Boolean, + toggleShowHidden: (Boolean) -> Unit, +) { + val columnState = rememberResponsiveColumnState() + ScreenScaffold( + scrollState = columnState, + ) { + ScalingLazyColumn( + modifier = Modifier.fillMaxSize(), + columnState = columnState, + ) { + item { + ToggleChip( + checked = showHidden, + onCheckedChanged = { toggleShowHidden(it) }, + label = "Show unstarted", + toggleControl = ToggleChipToggleControl.Switch, + ) + } + } + } +} \ 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 new file mode 100644 index 000000000..eb3e5177c --- /dev/null +++ b/wear/src/main/java/org/tasks/presentation/screens/SettingsViewModel.kt @@ -0,0 +1,71 @@ +package org.tasks.presentation.screens + +import android.app.Application +import androidx.datastore.core.DataStore +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.data.ProtoDataStoreHelper.protoDataStore +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.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.tasks.GrpcProto +import org.tasks.GrpcProto.Settings +import org.tasks.WearServiceGrpcKt +import org.tasks.copy +import org.tasks.extensions.wearDataLayerRegistry + +data class ViewState( + val initialized: Boolean = false, + val settings: Settings = Settings.getDefaultInstance(), +) + +@OptIn(ExperimentalHorologistApi::class) +class SettingsViewModel( + application: Application, +) : AndroidViewModel(application) { + private val registry = application.wearDataLayerRegistry(viewModelScope) + private val wearService: WearServiceGrpcKt.WearServiceCoroutineStub = registry.grpcClient( + nodeId = TargetNodeId.PairedPhone, + coroutineScope = viewModelScope, + ) { + WearServiceGrpcKt.WearServiceCoroutineStub(it) + } + private val settingsFlow: DataStore by lazy { + registry.protoDataStore(viewModelScope) + } + private val _viewState = MutableStateFlow(ViewState()) + val viewState = _viewState.asStateFlow() + + init { + registry + .protoFlow(TargetNodeId.PairedPhone) + .onEach { newSettings -> + _viewState.update { + it.copy( + initialized = true, + settings = newSettings, + ) + } + } + .launchIn(viewModelScope) + } + + fun setShowHidden(showHidden: Boolean) = viewModelScope.launch { + wearService.updateSettings( + GrpcProto.UpdateSettingsRequest.newBuilder() + .setSettings( + _viewState.value.settings.copy { + this.showHidden = showHidden + } + ) + .build() + ) + } +} \ No newline at end of file