mirror of https://github.com/tasks/tasks
WearOS task edit screen - WIP
parent
b6f1722350
commit
4e7e05c8af
@ -0,0 +1,123 @@
|
||||
package org.tasks.presentation.screens
|
||||
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.app.RemoteInput
|
||||
import android.content.Intent
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.Button
|
||||
import androidx.wear.compose.material.ButtonDefaults
|
||||
import androidx.wear.compose.material.CircularProgressIndicator
|
||||
import androidx.wear.compose.material.Text
|
||||
import androidx.wear.input.RemoteInputIntentHelper
|
||||
import androidx.wear.input.wearableExtender
|
||||
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.fillMaxRectangle
|
||||
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
|
||||
import org.tasks.presentation.components.Card
|
||||
import org.tasks.presentation.components.Checkbox
|
||||
|
||||
@OptIn(ExperimentalHorologistApi::class)
|
||||
@Composable
|
||||
fun TaskEditScreen(
|
||||
uiState: UiState,
|
||||
setTitle: (String) -> Unit,
|
||||
save: () -> Unit,
|
||||
) {
|
||||
if (uiState.loading) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxRectangle(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
val columnState = rememberResponsiveColumnState(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
)
|
||||
val keyboardInputRequest = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) { result: ActivityResult ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val text =
|
||||
result
|
||||
.data
|
||||
?.let { RemoteInput.getResultsFromIntent(it) }
|
||||
?.getCharSequence("input")
|
||||
?: return@rememberLauncherForActivityResult
|
||||
setTitle(text.toString())
|
||||
}
|
||||
}
|
||||
ScreenScaffold(
|
||||
scrollState = columnState,
|
||||
) {
|
||||
ScalingLazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
columnState = columnState,
|
||||
) {
|
||||
item {
|
||||
Text("New task")
|
||||
}
|
||||
item {
|
||||
Card(
|
||||
icon = {
|
||||
Checkbox(
|
||||
completed = uiState.completed,
|
||||
repeating = uiState.repeating,
|
||||
priority = uiState.priority,
|
||||
toggleComplete = {},
|
||||
)
|
||||
},
|
||||
content = {
|
||||
Text(
|
||||
text = uiState.title,
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
)
|
||||
},
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
item {
|
||||
Button(
|
||||
onClick = { save() },
|
||||
colors = ButtonDefaults.buttonColors(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text("Save")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if (uiState.isNew) {
|
||||
val intent: Intent = RemoteInputIntentHelper.createActionRemoteInputIntent()
|
||||
val remoteInputs: List<RemoteInput> = listOf(
|
||||
RemoteInput
|
||||
.Builder("input")
|
||||
.setLabel("Enter title")
|
||||
.setAllowFreeFormInput(true)
|
||||
.wearableExtender {
|
||||
setInputActionType(EditorInfo.IME_ACTION_DONE)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
RemoteInputIntentHelper.putRemoteInputsExtra(intent, remoteInputs)
|
||||
keyboardInputRequest.launch(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
package org.tasks.presentation.screens
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.android.horologist.annotations.ExperimentalHorologistApi
|
||||
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.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.tasks.GrpcProto
|
||||
import org.tasks.WearServiceGrpcKt
|
||||
import org.tasks.extensions.wearDataLayerRegistry
|
||||
import timber.log.Timber
|
||||
|
||||
data class UiState(
|
||||
val isNew: Boolean,
|
||||
val loading: Boolean = !isNew,
|
||||
val completed: Boolean = false,
|
||||
val repeating: Boolean = false,
|
||||
val priority: Int = 0,
|
||||
val title: String = "",
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalHorologistApi::class)
|
||||
class TaskEditViewModel(
|
||||
applicationContext: Context,
|
||||
private val taskId: Long,
|
||||
) : ViewModel() {
|
||||
private val _uiState = MutableStateFlow(UiState(isNew = taskId == 0L))
|
||||
val uiState = _uiState.asStateFlow()
|
||||
private val registry = applicationContext.wearDataLayerRegistry(viewModelScope)
|
||||
|
||||
private val wearService : WearServiceGrpcKt.WearServiceCoroutineStub = registry.grpcClient(
|
||||
nodeId = TargetNodeId.PairedPhone,
|
||||
coroutineScope = viewModelScope,
|
||||
) {
|
||||
WearServiceGrpcKt.WearServiceCoroutineStub(it)
|
||||
}
|
||||
|
||||
init {
|
||||
if (taskId > 0) {
|
||||
viewModelScope.launch {
|
||||
val task = wearService
|
||||
.getTask(GrpcProto.GetTaskRequest.newBuilder().setTaskId(taskId).build())
|
||||
Timber.d("Received $task")
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
loading = false,
|
||||
completed = task.completed,
|
||||
title = task.title,
|
||||
repeating = task.repeating,
|
||||
priority = task.priority,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun save(onComplete: () -> Unit) = viewModelScope.launch {
|
||||
val state = uiState.value
|
||||
wearService.saveTask(
|
||||
GrpcProto.SaveTaskRequest.newBuilder()
|
||||
.setTitle(state.title)
|
||||
.build()
|
||||
)
|
||||
onComplete()
|
||||
}
|
||||
|
||||
fun setTitle(title: String) {
|
||||
_uiState.update { it.copy(title = title) }
|
||||
}
|
||||
}
|
||||
|
||||
class TaskEditViewModelFactory(
|
||||
private val applicationContext: Context,
|
||||
private val taskId: Long,
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
if (modelClass.isAssignableFrom(TaskEditViewModel::class.java)) {
|
||||
return TaskEditViewModel(
|
||||
applicationContext = applicationContext,
|
||||
taskId = taskId,
|
||||
) as T
|
||||
}
|
||||
throw IllegalArgumentException("Unknown ViewModel class")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue