Skip to content

Commit

Permalink
Adds a signal chart to the Cellular UI
Browse files Browse the repository at this point in the history
  • Loading branch information
christianrowlands committed Jan 12, 2024
1 parent f6a9f04 commit 03e57d1
Show file tree
Hide file tree
Showing 15 changed files with 249 additions and 67 deletions.
4 changes: 2 additions & 2 deletions networksurvey/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2'

implementation 'app.futured.donut:donut:2.2.3'
implementation("com.patrykandpatrick.vico:core:2.0.0-alpha.5")
implementation("com.patrykandpatrick.vico:compose-m2:2.0.0-alpha.5")
implementation 'com.patrykandpatrick.vico:core:2.0.0-alpha.5'
implementation 'com.patrykandpatrick.vico:compose-m3:2.0.0-alpha.5'

implementation 'com.github.yuriy-budiyev:code-scanner:2.1.2'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.craxiom.networksurvey.fragments;

import static com.craxiom.networksurvey.ui.ASignalChartViewModelKt.UNKNOWN_RSSI;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
Expand Down Expand Up @@ -48,6 +50,8 @@
import com.craxiom.networksurvey.model.CellularRecordWrapper;
import com.craxiom.networksurvey.model.NrRecordWrapper;
import com.craxiom.networksurvey.services.NetworkSurveyService;
import com.craxiom.networksurvey.ui.cellular.CellularChartViewModel;
import com.craxiom.networksurvey.ui.cellular.ComposeFunctions;
import com.craxiom.networksurvey.util.CellularUtils;
import com.craxiom.networksurvey.util.ColorUtils;
import com.craxiom.networksurvey.util.MathUtils;
Expand Down Expand Up @@ -88,6 +92,7 @@ public class NetworkDetailsFragment extends AServiceDataFragment implements ICel

private FragmentNetworkDetailsBinding binding;
private CellularViewModel viewModel;
private CellularChartViewModel chartViewModel;

@Override
public void onCreate(@Nullable Bundle savedInstanceState)
Expand All @@ -109,13 +114,17 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
final ViewModelStoreOwner viewModelStoreOwner = NavHostFragment.findNavController(this).getViewModelStoreOwner(R.id.nav_graph);
final ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStoreOwner);
viewModel = viewModelProvider.get(getClass().getName() + subscriptionId, CellularViewModel.class);
chartViewModel = viewModelProvider.get(getClass().getName() + "cellular_chart" + subscriptionId, CellularChartViewModel.class);

initializeLocationTextView();

initializeUiListeners();

initializeObservers();

chartViewModel.addInitialRssi(UNKNOWN_RSSI);
ComposeFunctions.setContent(binding.composeView, chartViewModel);

return binding.getRoot();
}

Expand Down Expand Up @@ -423,6 +432,9 @@ private void updateServingCellProtocol(CellularProtocol protocol)
{
case NONE:
titleTextView.setText(R.string.card_title_cellular_details_initial);

chartViewModel.setChartTitle("RSSI");
chartViewModel.clearChart();
break;

case GSM:
Expand All @@ -435,12 +447,20 @@ private void updateServingCellProtocol(CellularProtocol protocol)
binding.taGroup.setVisibility(View.GONE);
binding.signalOneLabel.setText(R.string.rssi_label);
binding.signalTwoGroup.setVisibility(View.GONE);

chartViewModel.setChartTitle("RSSI");
chartViewModel.clearChart();
chartViewModel.setMinRssi(protocol.getMinSignalOne());
chartViewModel.setMaxRssi(protocol.getMinSignalOne() + protocol.getMaxNormalizedSignalOne());
break;

case CDMA:
binding.enbIdGroup.setVisibility(View.GONE);
binding.sectorIdGroup.setVisibility(View.GONE);
binding.signalTwoGroup.setVisibility(View.GONE);

chartViewModel.setChartTitle("RSSI");
chartViewModel.clearChart();
break;

case UMTS:
Expand All @@ -454,6 +474,11 @@ private void updateServingCellProtocol(CellularProtocol protocol)
binding.signalOneLabel.setText(R.string.rssi_label);
binding.signalTwoLabel.setText(R.string.rscp_label);
binding.signalTwoGroup.setVisibility(View.VISIBLE);

chartViewModel.setChartTitle("RSCP");
chartViewModel.clearChart();
chartViewModel.setMinRssi(protocol.getMinSignalOne());
chartViewModel.setMaxRssi(protocol.getMinSignalOne() + protocol.getMaxNormalizedSignalOne());
break;

case LTE:
Expand All @@ -467,6 +492,11 @@ private void updateServingCellProtocol(CellularProtocol protocol)
binding.signalOneLabel.setText(R.string.rsrp_label);
binding.signalTwoLabel.setText(R.string.rsrq_label);
binding.signalTwoGroup.setVisibility(View.VISIBLE);

chartViewModel.setChartTitle("RSRP");
chartViewModel.clearChart();
chartViewModel.setMinRssi(protocol.getMinSignalOne());
chartViewModel.setMaxRssi(protocol.getMinSignalOne() + protocol.getMaxNormalizedSignalOne());
break;

case NR:
Expand All @@ -480,6 +510,11 @@ private void updateServingCellProtocol(CellularProtocol protocol)
binding.signalOneLabel.setText(R.string.ss_rsrp_label);
binding.signalTwoLabel.setText(R.string.ss_rsrq_label);
binding.signalTwoGroup.setVisibility(View.VISIBLE);

chartViewModel.setChartTitle("SS RSRP");
chartViewModel.clearChart();
chartViewModel.setMinRssi(protocol.getMinSignalOne());
chartViewModel.setMaxRssi(protocol.getMinSignalOne() + protocol.getMaxNormalizedSignalOne());
break;
}
}
Expand Down Expand Up @@ -595,6 +630,11 @@ private void processGsmServingCell(GsmRecordData data)
viewModel.setPci(data.hasBsic() ? ParserUtils.bsicToString(data.getBsic().getValue()) : "");

viewModel.setSignalOne(data.hasSignalStrength() ? (int) data.getSignalStrength().getValue() : null);

if (data.hasSignalStrength())
{
chartViewModel.addNewRssi((int) data.getSignalStrength().getValue());
}
}

/**
Expand All @@ -615,6 +655,11 @@ private void processUmtsServingCell(UmtsRecordData data)

viewModel.setSignalOne(data.hasSignalStrength() ? (int) data.getSignalStrength().getValue() : null);
viewModel.setSignalTwo(data.hasRscp() ? (int) data.getRscp().getValue() : null);

if (data.hasSignalStrength())
{
chartViewModel.addNewRssi((int) data.getSignalStrength().getValue());
}
}

/**
Expand Down Expand Up @@ -656,6 +701,8 @@ private void processLteServingCell(LteRecordData data)

viewModel.setSignalOne(data.hasRsrp() ? (int) data.getRsrp().getValue() : null);
viewModel.setSignalTwo(data.hasRsrq() ? (int) data.getRsrq().getValue() : null);

if (data.hasRsrp()) chartViewModel.addNewRssi((int) data.getRsrp().getValue());
}

/**
Expand Down Expand Up @@ -706,6 +753,8 @@ private void processNrServingCell(NrRecordData data, int[] bands)

viewModel.setSignalOne(data.hasSsRsrp() ? (int) data.getSsRsrp().getValue() : null);
viewModel.setSignalTwo(data.hasSsRsrq() ? (int) data.getSsRsrq().getValue() : null);

if (data.hasSsRsrp()) chartViewModel.addNewRssi((int) data.getSsRsrp().getValue());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.craxiom.networksurvey.ui

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.craxiom.networksurvey.ui.wifi.MAX_WIFI_RSSI
import com.craxiom.networksurvey.ui.wifi.MIN_WIFI_RSSI
import com.patrykandpatrick.vico.core.model.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.model.LineCartesianLayerModel
import kotlinx.coroutines.Dispatchers
Expand All @@ -22,15 +24,30 @@ private const val CHART_WIDTH = 120
private const val UPDATE_FREQUENCY = 1000L

/**
* Abstract base class for the view model for Details screen that contains a chart.
* Abstract base class for the view model for a signal chart.
*/
internal abstract class AChartDetailsViewModel : ViewModel() {
abstract class ASignalChartViewModel : ViewModel() {

internal val modelProducer = CartesianChartModelProducer.build()

private val _scanRateSeconds = MutableStateFlow(-1)
val scanRate = _scanRateSeconds.asStateFlow()

private val _maxRssi = MutableStateFlow(MAX_WIFI_RSSI)

/**
* The maximum RSSI value to display in the chart. This will be the top end of the chart and
* values outside this range will be reduced to this value.
*/
val maxRssi = _maxRssi.asStateFlow()
private val _minRssi = MutableStateFlow(MIN_WIFI_RSSI)

/**
* The minimum RSSI value to display in the chart. This will be the bottom end of the chart and
* values outside this range will be increased to this value.
*/
val minRssi = _minRssi.asStateFlow()

// This is the RSSI that is displayed in the details header. It is not necessarily the same
// as the RSSI that is displayed in the chart because on the chart we have to limit the range
// of the RSSI values.
Expand Down Expand Up @@ -62,16 +79,38 @@ internal abstract class AChartDetailsViewModel : ViewModel() {
}

/**
* The maximum RSSI value to display in the chart. This will be the top end of the chart and
* Sets the scan rate in seconds.
*/
fun setScanRateSeconds(scanRateSeconds: Int) {
_scanRateSeconds.value = scanRateSeconds
}

/**
* Sets the maximum RSSI value to display in the chart. This will be the top end of the chart and
* values outside this range will be reduced to this value.
*/
abstract fun getMaxRssi(): Float
fun setMaxRssi(maxRssi: Float) {
_maxRssi.value = maxRssi
}

/**
* The minimum RSSI value to display in the chart. This will be the bottom end of the chart and
* Sets the minimum RSSI value to display in the chart. This will be the bottom end of the chart and
* values outside this range will be increased to this value.
*/
abstract fun getMinRssi(): Float
fun setMinRssi(minRssi: Float) {
_minRssi.value = minRssi
}

/**
* Clears the chart of all data and resets the stored RSSI values.
*/
fun clearChart() {
for (i in 0 until CHART_WIDTH) {
addRssiToChart(UNKNOWN_RSSI)
}
_latestChartRssi.value = UNKNOWN_RSSI
_rssi.value = UNKNOWN_RSSI
}

/**
* Adds the initial RSSI value to the chart. This is used to make sure that the chart is
Expand Down Expand Up @@ -114,14 +153,15 @@ internal abstract class AChartDetailsViewModel : ViewModel() {
}
}

val minRssi = getMinRssi()
val maxRssi = getMaxRssi()

if (rssi != UNKNOWN_RSSI) {
if (rssi < minRssi) {
rssiToChart = minRssi
} else if (rssi > maxRssi) {
rssiToChart = maxRssi
val minRssiValue = minRssi.value
if (rssi < minRssiValue) {
rssiToChart = minRssiValue
} else {
val maxRssiValue = maxRssi.value
if (rssi > maxRssiValue) {
rssiToChart = maxRssiValue
}
}
}

Expand All @@ -142,13 +182,6 @@ internal abstract class AChartDetailsViewModel : ViewModel() {

modelProducer.tryRunTransaction { add(lineLayerModelPartial) }
}

/**
* Sets the scan rate in seconds.
*/
fun setScanRateSeconds(scanRateSeconds: Int) {
_scanRateSeconds.value = scanRateSeconds
}
}

fun <E> dequeLimiter(limit: Int): ReadWriteProperty<Any?, ArrayDeque<E>> =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.craxiom.networksurvey.ui.wifi
package com.craxiom.networksurvey.ui

import android.graphics.Paint
import androidx.compose.foundation.isSystemInDarkTheme
Expand All @@ -21,7 +21,7 @@ import com.patrykandpatrick.vico.core.context.DrawContext
* A Circle component that can be used to draw a point on a canvas. This could be used instead of
* drawing lines between the points.
*/
class CircleComponentGpt(
class CircleComponent(
private val radius: Float,
private val circleColor: Int
) : Component() {
Expand All @@ -39,8 +39,7 @@ class CircleComponentGpt(
) {
val centerX = (left + right) / 2
val centerY = (top + bottom) / 2
paint.alpha =
(opacity * 255).toInt() // Adjust paint opacity based on the provided opacity parameter
paint.alpha = (opacity * 255).toInt()
context.canvas.drawCircle(centerX, centerY, radius, paint)
}
}
Expand Down Expand Up @@ -74,7 +73,7 @@ internal fun rememberChartStyle(
thicknessDp = 3f,
shader = DynamicShaders.color(lineChartColor),
backgroundShader = null,
//point = CircleComponentGpt(5f, lineChartColor.toArgb()),
//point = CircleComponent(5f, lineChartColor.toArgb()),
)
},
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.craxiom.networksurvey.ui.wifi
package com.craxiom.networksurvey.ui

import android.graphics.Typeface
import androidx.compose.material3.MaterialTheme
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.craxiom.networksurvey.ui.wifi
package com.craxiom.networksurvey.ui

import androidx.compose.animation.core.snap
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.patrykandpatrick.vico.compose.axis.vertical.rememberStartAxis
import com.patrykandpatrick.vico.compose.chart.CartesianChartHost
import com.patrykandpatrick.vico.compose.chart.edges.rememberFadingEdges
Expand All @@ -16,26 +18,27 @@ import com.patrykandpatrick.vico.core.axis.AxisItemPlacer
import com.patrykandpatrick.vico.core.axis.vertical.VerticalAxis
import com.patrykandpatrick.vico.core.chart.layout.HorizontalLayout
import com.patrykandpatrick.vico.core.chart.values.AxisValueOverrider
import com.patrykandpatrick.vico.core.model.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.model.LineCartesianLayerModel

/**
* A chart that shows the RSSI values over time.
* A chart that shows signal values (e.g. RSSI) over time.
*
* @param modelProducer The model producer that will provide the data to be displayed in the chart.
* @param viewModel The view model that contains the data to display.
*/
@Composable
internal fun WifiRssiChart(
modelProducer: CartesianChartModelProducer,
internal fun SignalChart(
viewModel: ASignalChartViewModel
) {
ComposeChart(modelProducer)
ComposeChart(viewModel)
}

@Composable
private fun ComposeChart(modelProducer: CartesianChartModelProducer) {
private fun ComposeChart(viewModel: ASignalChartViewModel) {
val maxRssi by viewModel.maxRssi.collectAsStateWithLifecycle()
val minRssi by viewModel.minRssi.collectAsStateWithLifecycle()

ProvideChartStyle(rememberChartStyle(chartColors)) {
CartesianChartHost(
modelProducer = modelProducer,
modelProducer = viewModel.modelProducer,
marker = rememberMarker(),
runInitialAnimation = false,
diffAnimationSpec = snap(),
Expand All @@ -44,7 +47,10 @@ private fun ComposeChart(modelProducer: CartesianChartModelProducer) {
chart =
rememberCartesianChart(
rememberLineCartesianLayer(
axisValueOverrider = axisValueOverrider,
axisValueOverrider = AxisValueOverrider.fixed(
maxY = maxRssi,
minY = minRssi,
),
//lines = remember(defaultLines) { defaultLines.map { it.copy(backgroundShader = null) } },
),
startAxis =
Expand All @@ -60,8 +66,4 @@ private fun ComposeChart(modelProducer: CartesianChartModelProducer) {

private val lineColor = Color(0xFF03A9F4)
private val chartColors = listOf(lineColor)
private val axisValueOverrider = AxisValueOverrider.fixed<LineCartesianLayerModel>(
maxY = MAX_WIFI_RSSI,
minY = MIN_WIFI_RSSI,
)
private val horizontalLayout = HorizontalLayout.fullWidth()
Loading

0 comments on commit 03e57d1

Please sign in to comment.