Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Charts #1198

Merged
merged 21 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b90f614
feat: Add more details screen for nodes
jamesarich Aug 1, 2024
7615066
feat: Sets background and content colors of the app bar.
jamesarich Aug 1, 2024
3241a50
Refactor: Use nodeDBbyNum to get node by number
jamesarich Aug 3, 2024
8a15143
Merge branch 'meshtastic:master' into graphs
Robert-0410 Aug 6, 2024
931d064
Merge branch 'graphs' of github.com:Robert-0410/Meshtastic-Android in…
Robert-0410 Aug 9, 2024
20daa95
Implementation of a first DeviceMetrics chart.
Robert-0410 Aug 17, 2024
a849b6a
Merge branch 'meshtastic:master' into graphs
Robert-0410 Aug 17, 2024
67f410c
Merge branch 'master' into graphs
thebentern Aug 18, 2024
252fecf
Merge branch 'master' into graphs
Robert-0410 Aug 18, 2024
c531860
Wrote a specific ViewModal that holds a List of DataEntries used for …
Robert-0410 Aug 21, 2024
69c7ea2
Merge branch 'master' into graphs
Robert-0410 Aug 21, 2024
e3499ea
Making detekt happy.
Robert-0410 Aug 21, 2024
2a4d6df
Implemented the Environment Metrics chart; the user is able to swipe …
Robert-0410 Aug 24, 2024
752eec3
Merge branch 'master' into graphs
Robert-0410 Aug 24, 2024
1c05fb3
Added tabs as a visual aid for navigating the pager.
Robert-0410 Aug 25, 2024
aa10fcd
Placed the header so we could see when we have no data.
Robert-0410 Aug 25, 2024
abf20d3
detekt fixes
andrekir Aug 25, 2024
3b39466
Merge branch 'master' into graphs
Robert-0410 Aug 25, 2024
dab9514
Addressed changes recommended by review.
Robert-0410 Aug 25, 2024
4fe12c0
Removed unused imports.
Robert-0410 Aug 29, 2024
bc208fa
Merge branch 'master' into graphs
Robert-0410 Aug 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.geeksville.mesh.model

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.geeksville.mesh.TelemetryProtos.Telemetry
import com.geeksville.mesh.database.MeshLogRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
import javax.inject.Inject


@HiltViewModel
class NodeDetailsViewModel @Inject constructor(
val nodeDB: NodeDB,
private val meshLogRepository: MeshLogRepository
) : ViewModel() {

private val _deviceMetrics = MutableStateFlow<List<Telemetry>>(emptyList())
val deviceMetrics: StateFlow<List<Telemetry>> = _deviceMetrics

private val _environmentMetrics = MutableStateFlow<List<Telemetry>>(emptyList())
val environmentMetrics: StateFlow<List<Telemetry>> = _environmentMetrics

/**
* Gets the short name of the node identified by `nodeNum`.
*/
@OptIn(ExperimentalCoroutinesApi::class)
suspend fun getNodeName(nodeNum: Int): String? {
return nodeDB.nodeDBbyNum.mapLatest { it[nodeNum] }.first()?.user?.shortName
}
Robert-0410 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Used to set the Node for which the user will see charts for.
*/
fun setSelectedNode(nodeNum: Int) {
viewModelScope.launch {
meshLogRepository.getTelemetryFrom(nodeNum).collect {
val deviceList = mutableListOf<Telemetry>()
val environmentList = mutableListOf<Telemetry>()
for (telemetry in it) {
if (telemetry.hasDeviceMetrics())
deviceList.add(telemetry)
/* Avoiding negative outliers */
if (telemetry.hasEnvironmentMetrics() && telemetry.environmentMetrics.relativeHumidity >= 0f)
environmentList.add(telemetry)
}
_deviceMetrics.value = deviceList
_environmentMetrics.value = environmentList
}
}
}
}
173 changes: 173 additions & 0 deletions app/src/main/java/com/geeksville/mesh/ui/NodeDetailsFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package com.geeksville.mesh.ui

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.model.NodeDetailsViewModel
import com.geeksville.mesh.ui.components.DeviceMetricsScreen
import com.geeksville.mesh.ui.components.EnvironmentMetricsScreen
import com.geeksville.mesh.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch

internal fun FragmentManager.navigateToNodeDetails(nodeNum: Int? = null) {
val nodeDetailsFragment = NodeDetailsFragment().apply {
arguments = bundleOf("nodeNum" to nodeNum)
}
beginTransaction()
.replace(R.id.mainActivityLayout, nodeDetailsFragment)
.addToBackStack(null)
.commit()
}

@AndroidEntryPoint
class NodeDetailsFragment : ScreenFragment("NodeDetails"), Logging {

private val model: NodeDetailsViewModel by viewModels()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val nodeNum = arguments?.getInt("nodeNum")
if (nodeNum != null)
model.setSelectedNode(nodeNum)
/* We only need to get the nodes name once. */
var nodeName: String? = ""
lifecycleScope.launch { nodeName = model.getNodeName(nodeNum ?: 0) }

Robert-0410 marked this conversation as resolved.
Show resolved Hide resolved
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
AppTheme {
NodeDetailsScreen(
model = model,
nodeName = nodeName,
navigateBack = {
parentFragmentManager.popBackStack()
}
)
}
}
}
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun NodeDetailsScreen(
model: NodeDetailsViewModel = hiltViewModel(),
nodeName: String?,
navigateBack: () -> Unit,
) {
val deviceMetrics by model.deviceMetrics.collectAsStateWithLifecycle()
val environmentMetrics by model.environmentMetrics.collectAsStateWithLifecycle()

val pagerState = rememberPagerState(pageCount = { 2 })

Scaffold(
/*
* NOTE: The bottom bar could be used to enable other actions such as clear or export data.
*/
topBar = {
TopAppBar(
backgroundColor = colorResource(R.color.toolbarBackground),
contentColor = colorResource(R.color.toolbarText),
title = {
Text(
text = "${stringResource(R.string.node_details)}: $nodeName",
)
HorizontalTabs(pagerState)
},
navigationIcon = {
IconButton(onClick = navigateBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.navigate_back),
)
}
}
)
},
) { innerPadding ->
HorizontalPager(state = pagerState) { page ->
when (page) {
0 -> DeviceMetricsScreen(
innerPadding = innerPadding,
telemetries = deviceMetrics
)
1 -> EnvironmentMetricsScreen(
innerPadding = innerPadding,
telemetries = environmentMetrics
)
}
}
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun HorizontalTabs(pagerState: PagerState) {

Row(
Modifier
.wrapContentHeight()
.fillMaxWidth()
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.Center
) {
repeat(pagerState.pageCount) { iteration ->
val color = if (pagerState.currentPage == iteration)
colorResource(R.color.toolbarText)
else
Color.LightGray

val imageVector = if (iteration == 0)
ImageVector.vectorResource(R.drawable.baseline_charging_station_24)
else
ImageVector.vectorResource(R.drawable.baseline_thermostat_24)
Icon(
imageVector = imageVector,
contentDescription = stringResource(R.string.tab),
Robert-0410 marked this conversation as resolved.
Show resolved Hide resolved
tint = color
)
}
}
}
9 changes: 9 additions & 0 deletions app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ class UsersFragment : ScreenFragment("Users"), Logging {
R.id.remote_admin -> {
navigateToRadioConfig(node)
}

R.id.more_details -> {
navigateToMoreDetails(node)
}
}
}
}
Expand All @@ -97,6 +101,11 @@ class UsersFragment : ScreenFragment("Users"), Logging {
parentFragmentManager.navigateToRadioConfig(node.num)
}

private fun navigateToMoreDetails(node: NodeInfo) {
info("calling MoreDetails --> destNum: ${node.num}")
parentFragmentManager.navigateToNodeDetails(node.num)
}


override fun onCreateView(
inflater: LayoutInflater,
Expand Down
Loading