feat: add nodelist sort options

This commit is contained in:
andrekir 2024-05-27 09:56:26 -03:00 committed by Andre K
parent f84a75569d
commit 4ceb4c5199
17 changed files with 368 additions and 89 deletions

View file

@ -37,6 +37,7 @@ import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.NodeInfo
import com.geeksville.mesh.R
import com.geeksville.mesh.ui.compose.ElevationInfo
@ -137,7 +138,7 @@ fun NodeInfo(
)
}
val style = if (nodeName == unknownLongName) {
val style = if (thatNodeInfo.user?.hwModel == MeshProtos.HardwareModel.UNSET) {
LocalTextStyle.current.copy(fontStyle = FontStyle.Italic)
} else {
LocalTextStyle.current

View file

@ -0,0 +1,89 @@
package com.geeksville.mesh.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Done
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
import com.geeksville.mesh.model.NodeSortOption
@Composable
internal fun NodeSortButton(
currentSortOption: NodeSortOption,
onSortSelected: (NodeSortOption) -> Unit,
includeUnknown: Boolean,
onToggleIncludeUnknown: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier) {
var expanded by remember { mutableStateOf(false) }
IconButton(onClick = { expanded = true }) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_twotone_sort_24),
contentDescription = null,
modifier = Modifier.heightIn(max = 48.dp),
tint = MaterialTheme.colors.onSurface
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.background(MaterialTheme.colors.background.copy(alpha = 1f))
) {
NodeSortOption.entries.forEach { sort ->
DropdownMenuItem(
onClick = {
onSortSelected(sort)
expanded = false
},
) {
Text(
text = stringResource(id = sort.stringRes),
fontWeight = if (sort == currentSortOption) FontWeight.Bold else null,
)
}
}
Divider()
DropdownMenuItem(
onClick = {
onToggleIncludeUnknown()
expanded = false
},
) {
Text(
text = stringResource(id = R.string.node_filter_include_unknown),
)
AnimatedVisibility(visible = includeUnknown) {
Icon(
imageVector = Icons.Default.Done,
contentDescription = null,
modifier = Modifier.padding(start = 4.dp),
)
}
}
}
}
}

View file

@ -7,21 +7,20 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.setFragmentResult
import androidx.lifecycle.asLiveData
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
@ -262,7 +261,7 @@ class UsersFragment : ScreenFragment("Users"), Logging {
binding.nodeFilter.initFilter()
model.filteredNodes.asLiveData().observe(viewLifecycleOwner) { nodeMap ->
nodesAdapter.onNodesChanged(nodeMap.values.toTypedArray())
nodesAdapter.onNodesChanged(nodeMap.toTypedArray())
}
model.localConfig.asLiveData().observe(viewLifecycleOwner) { config ->
@ -343,17 +342,24 @@ class UsersFragment : ScreenFragment("Users"), Logging {
private fun ComposeView.initFilter() {
this.setContent {
val filterText by model.nodeFilterText.collectAsState()
val nodeViewState by model.nodeViewState.collectAsStateWithLifecycle()
AppTheme {
Box(
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.shadow(8.dp)
) {
NodeFilterTextField(
filterText = filterText,
onTextChanged = { model.setNodeFilterText(it) }
filterText = nodeViewState.filter,
onTextChanged = model::setNodeFilterText,
modifier = Modifier.weight(1f)
)
NodeSortButton(
currentSortOption = nodeViewState.sort,
onSortSelected = model::setSortOption,
includeUnknown = nodeViewState.includeUnknown,
onToggleIncludeUnknown = model::toggleIncludeUnknown,
)
}
}

View file

@ -13,8 +13,14 @@ import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
@ -26,15 +32,17 @@ import com.geeksville.mesh.ui.theme.AppTheme
@Composable
fun NodeFilterTextField(
filterText : String = "",
onTextChanged : (String) -> Unit
filterText : String,
onTextChanged : (String) -> Unit,
modifier: Modifier = Modifier,
) {
val focusManager = LocalFocusManager.current
var isFocused by remember { mutableStateOf(false) }
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
modifier = modifier
.heightIn(max = 48.dp)
.onFocusEvent { isFocused = it.isFocused }
.background(MaterialTheme.colors.background),
value = filterText,
placeholder = {
@ -44,13 +52,22 @@ fun NodeFilterTextField(
color = MaterialTheme.colors.onBackground.copy(alpha = 0.35F)
)
},
leadingIcon = {
Icon(
Icons.Default.Search,
contentDescription = stringResource(id = R.string.node_filter_placeholder),
)
},
onValueChange = onTextChanged,
trailingIcon = {
if (filterText.isNotEmpty()) {
if (filterText.isNotEmpty() || isFocused) {
Icon(
Icons.Default.Clear,
contentDescription = stringResource(id = R.string.desc_node_filter_clear),
modifier = Modifier.clickable { onTextChanged("") }
modifier = Modifier.clickable {
onTextChanged("")
focusManager.clearFocus()
}
)
}
},

View file

@ -1,4 +1,4 @@
package com.geeksville.mesh.ui.map.components
package com.geeksville.mesh.ui.map
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
@Composable
fun CacheLayout(
internal fun CacheLayout(
cacheEstimate: String,
onExecuteJob: () -> Unit,
onCancelDownload: () -> Unit,

View file

@ -1,4 +1,4 @@
package com.geeksville.mesh.ui.map.components
package com.geeksville.mesh.ui.map
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutSlowInEasing
@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource
import com.geeksville.mesh.R
@Composable
fun DownloadButton(
internal fun DownloadButton(
enabled: Boolean,
onClick: () -> Unit,
) {

View file

@ -1,4 +1,4 @@
package com.geeksville.mesh.ui.map.components
package com.geeksville.mesh.ui.map
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@ -42,7 +42,7 @@ import com.geeksville.mesh.util.CustomRecentEmojiProvider
import com.geeksville.mesh.waypoint
@Composable
fun EditWaypointDialog(
internal fun EditWaypointDialog(
waypoint: Waypoint,
onSendClicked: (Waypoint) -> Unit,
onDeleteClicked: (Waypoint) -> Unit,

View file

@ -50,19 +50,14 @@ import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.model.map.CustomTileSource
import com.geeksville.mesh.model.map.MarkerWithLabel
import com.geeksville.mesh.ui.MessagesFragment
import com.geeksville.mesh.ui.ScreenFragment
import com.geeksville.mesh.ui.map.components.CacheLayout
import com.geeksville.mesh.ui.map.components.DownloadButton
import com.geeksville.mesh.ui.map.components.EditWaypointDialog
import com.geeksville.mesh.ui.components.IconButton
import com.geeksville.mesh.ui.map.components.rememberMapViewWithLifecycle
import com.geeksville.mesh.ui.theme.AppTheme
import com.geeksville.mesh.util.SqlTileWriterExt
import com.geeksville.mesh.util.requiredZoomLevel
import com.geeksville.mesh.util.formatAgo
import com.geeksville.mesh.util.zoomIn
import com.geeksville.mesh.waypoint
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import org.osmdroid.bonuspack.utils.BonusPackHelper.getBitmapFromVectorDrawable
@ -90,7 +85,6 @@ import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
import java.io.File
import java.text.DateFormat
@AndroidEntryPoint
class MapFragment : ScreenFragment("Map Fragment"), Logging {
@ -104,7 +98,7 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
AppCompatTheme {
AppTheme {
MapView(model)
}
}
@ -192,7 +186,7 @@ fun MapView(
requestPermissionAndToggleLauncher.launch(context.getLocationPermissions())
}
val nodes by model.nodeDB.nodes.collectAsStateWithLifecycle()
val nodes by model.filteredNodes.collectAsStateWithLifecycle(emptyList())
val waypoints by model.waypoints.observeAsState(emptyMap())
var showDownloadButton: Boolean by remember { mutableStateOf(false) }
@ -462,7 +456,7 @@ fun MapView(
}
with(map) {
UpdateMarkers(onNodesChanged(nodes.values), onWaypointChanged(waypoints.values))
UpdateMarkers(onNodesChanged(nodes), onWaypointChanged(waypoints.values))
}
// private fun addWeatherLayer() {
@ -482,7 +476,7 @@ fun MapView(
// }
fun MapView.zoomToNodes() {
val nodeMarkers = onNodesChanged(nodes.values)
val nodeMarkers = onNodesChanged(nodes)
if (nodeMarkers.isNotEmpty()) {
val box = BoundingBox.fromGeoPoints(nodeMarkers.map { it.position })
val center = GeoPoint(box.centerLatitude, box.centerLongitude)

View file

@ -1,4 +1,4 @@
package com.geeksville.mesh.ui.map.components
package com.geeksville.mesh.ui.map
import android.annotation.SuppressLint
import android.content.Context
@ -32,7 +32,7 @@ private fun PowerManager.WakeLock.safeRelease() {
}
@Composable
fun rememberMapViewWithLifecycle(context: Context): MapView {
internal fun rememberMapViewWithLifecycle(context: Context): MapView {
val mapView = remember {
MapView(context).apply {
clipToOutline = true