mirror of https://github.com/tasks/tasks
Implement drawer for Android Wear
parent
689cd20a88
commit
105757af53
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package org.tasks.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EmptyCard() = Card(onClick = {}) { }
|
||||||
@ -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<ListItem>,
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<ListItem>? = null
|
||||||
|
val uiItems: Flow<PagingData<ListItem>> = 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<LastUpdate>(TargetNodeId.PairedPhone)
|
||||||
|
.onEach { invalidate() }
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
registry
|
||||||
|
.protoFlow<Settings>(TargetNodeId.PairedPhone)
|
||||||
|
.onEach { invalidate() }
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invalidate() {
|
||||||
|
pagingSource?.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue