From d95ef5f3cd0e4e3a82cb2a0716725d864b5e1a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylwester=20Zieli=C5=84ski?= Date: Mon, 11 Apr 2022 15:06:55 +0200 Subject: [PATCH] Add zoom button to HRS profile. --- .../android/theme/view/SectionTitle.kt | 14 +++--- .../android/hrs/view/HRSContentView.kt | 30 +++++++++++-- .../nordicsemi/android/hrs/view/HRSScreen.kt | 7 ++- .../android/hrs/view/HRSScreenViewEvent.kt | 2 + .../nordicsemi/android/hrs/view/HRSState.kt | 5 ++- .../android/hrs/view/LineChartView.kt | 43 +++++++++++++++---- .../android/hrs/viewmodel/HRSViewModel.kt | 10 ++++- .../src/main/res/drawable/ic_zoom_in.xml | 13 ++++++ .../src/main/res/drawable/ic_zoom_out.xml | 10 +++++ profile_hrs/src/main/res/values/strings.xml | 2 + 10 files changed, 110 insertions(+), 26 deletions(-) create mode 100644 profile_hrs/src/main/res/drawable/ic_zoom_in.xml create mode 100644 profile_hrs/src/main/res/drawable/ic_zoom_out.xml diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SectionTitle.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SectionTitle.kt index 29fdc4ff..47395fbf 100644 --- a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SectionTitle.kt +++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SectionTitle.kt @@ -3,12 +3,7 @@ package no.nordicsemi.android.theme.view import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -28,6 +23,7 @@ import androidx.compose.ui.unit.sp fun SectionTitle( @DrawableRes resId: Int, title: String, + menu: @Composable (() -> Unit)? = null, modifier: Modifier = Modifier.fillMaxWidth() ) { Row( @@ -49,10 +45,12 @@ fun SectionTitle( Spacer(modifier = Modifier.size(8.dp)) Text( text = title, - textAlign = TextAlign.Center, + textAlign = TextAlign.Start, fontSize = 24.sp, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f) ) + menu?.invoke() } } diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt index 981687e5..3b9577dc 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt @@ -5,10 +5,13 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -19,17 +22,22 @@ import no.nordicsemi.android.theme.view.BatteryLevelView import no.nordicsemi.android.theme.view.SectionTitle @Composable -internal fun HRSContentView(state: HRSData, onEvent: (HRSScreenViewEvent) -> Unit) { +internal fun HRSContentView(state: HRSData, zoomIn: Boolean, onEvent: (HRSScreenViewEvent) -> Unit) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) ) { + ScreenSection { - SectionTitle(resId = R.drawable.ic_chart_line, title = "Data") + SectionTitle( + resId = R.drawable.ic_chart_line, + title = stringResource(id = R.string.hrs_section_data), + menu = { Menu(zoomIn, onEvent) } + ) Spacer(modifier = Modifier.height(16.dp)) - LineChartView(state) + LineChartView(state, zoomIn) } Spacer(modifier = Modifier.height(16.dp)) @@ -48,8 +56,22 @@ internal fun HRSContentView(state: HRSData, onEvent: (HRSScreenViewEvent) -> Uni } } +@Composable +private fun Menu(zoomIn: Boolean, onEvent: (HRSScreenViewEvent) -> Unit) { + val icon = when (zoomIn) { + true -> R.drawable.ic_zoom_out + false -> R.drawable.ic_zoom_in + } + IconButton(onClick = { onEvent(SwitchZoomEvent) }) { + Icon( + painter = painterResource(id = icon), + contentDescription = stringResource(id = R.string.hrs_zoom_icon) + ) + } +} + @Preview @Composable private fun Preview() { - HRSContentView(state = HRSData()) { } + HRSContentView(state = HRSData(), zoomIn = false) { } } diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt index 97cd73b0..37ae8175 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt @@ -11,12 +11,11 @@ import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.hrs.R import no.nordicsemi.android.hrs.viewmodel.HRSViewModel import no.nordicsemi.android.service.* -import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar -import no.nordicsemi.ui.scanner.ui.DeviceConnectingView -import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView +import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.ui.scanner.ui.Reason @Composable @@ -40,7 +39,7 @@ fun HRSScreen() { is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) - is SuccessResult -> HRSContentView(state.result.data) { viewModel.onEvent(it) } + is SuccessResult -> HRSContentView(state.result.data, state.zoomIn) { viewModel.onEvent(it) } } }.exhaustive } diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreenViewEvent.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreenViewEvent.kt index de5e973f..41d2a026 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreenViewEvent.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreenViewEvent.kt @@ -2,6 +2,8 @@ package no.nordicsemi.android.hrs.view internal sealed class HRSScreenViewEvent +internal object SwitchZoomEvent : HRSScreenViewEvent() + internal object DisconnectEvent : HRSScreenViewEvent() internal object NavigateUpEvent : HRSScreenViewEvent() diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSState.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSState.kt index 1038c981..c2072cf5 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSState.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSState.kt @@ -5,6 +5,9 @@ import no.nordicsemi.android.service.BleManagerResult internal sealed class HRSViewState -internal data class WorkingState(val result: BleManagerResult) : HRSViewState() +internal data class WorkingState( + val result: BleManagerResult, + val zoomIn: Boolean = false, +) : HRSViewState() internal object NoDeviceState : HRSViewState() diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/LineChartView.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/LineChartView.kt index 4fa231eb..3cb6ad22 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/LineChartView.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/LineChartView.kt @@ -17,24 +17,31 @@ import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.interfaces.datasets.ILineDataSet import no.nordicsemi.android.hrs.data.HRSData -import java.util.* private const val X_AXIS_ELEMENTS_COUNT = 40f +private const val AXIS_MIN = 0 +private const val AXIS_MAX = 300 + @Composable -internal fun LineChartView(state: HRSData) { +internal fun LineChartView(state: HRSData, zoomIn: Boolean,) { val items = state.heartRates.takeLast(X_AXIS_ELEMENTS_COUNT.toInt()).reversed() val isSystemInDarkTheme = isSystemInDarkTheme() AndroidView( modifier = Modifier .fillMaxWidth() .height(300.dp), - factory = { createLineChartView(isSystemInDarkTheme, it, items) }, - update = { updateData(items, it) } + factory = { createLineChartView(isSystemInDarkTheme, it, items, zoomIn) }, + update = { updateData(items, it, zoomIn) } ) } -internal fun createLineChartView(isDarkTheme: Boolean, context: Context, points: List): LineChart { +internal fun createLineChartView( + isDarkTheme: Boolean, + context: Context, + points: List, + zoomIn: Boolean +): LineChart { return LineChart(context).apply { description.isEnabled = false @@ -73,8 +80,8 @@ internal fun createLineChartView(isDarkTheme: Boolean, context: Context, points: axisLeft.apply { enableGridDashedLine(10f, 10f, 0f) - axisMaximum = 300f - axisMinimum = 100f + axisMaximum = points.getMax(zoomIn) + axisMinimum = points.getMin(zoomIn) } axisRight.isEnabled = false @@ -153,12 +160,16 @@ internal fun createLineChartView(isDarkTheme: Boolean, context: Context, points: } } -private fun updateData(points: List, chart: LineChart) { +private fun updateData(points: List, chart: LineChart, zoomIn: Boolean) { val entries = points.mapIndexed { i, v -> Entry(-i.toFloat(), v.toFloat()) }.reversed() with(chart) { + axisLeft.apply { + axisMaximum = points.getMax(zoomIn) + axisMinimum = points.getMin(zoomIn) + } if (data != null && data.dataSetCount > 0) { val set1 = data!!.getDataSetByIndex(0) as LineDataSet set1.values = entries @@ -169,3 +180,19 @@ private fun updateData(points: List, chart: LineChart) { } } } + +private fun List.getMin(zoomIn: Boolean): Float { + return if (zoomIn) { + minOrNull() ?: AXIS_MIN + } else { + AXIS_MIN + }.toFloat() +} + +private fun List.getMax(zoomIn: Boolean): Float { + return if (zoomIn) { + maxOrNull() ?: AXIS_MAX + } else { + AXIS_MAX + }.toFloat() +} diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt index 59fb62d5..504d98de 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt @@ -31,7 +31,8 @@ internal class HRSViewModel @Inject constructor( } repository.data.onEach { - _state.value = WorkingState(it) + val zoomIn = (_state.value as? WorkingState)?.zoomIn ?: false + _state.value = WorkingState(it, zoomIn) }.launchIn(viewModelScope) } @@ -57,9 +58,16 @@ internal class HRSViewModel @Inject constructor( DisconnectEvent -> disconnect() NavigateUpEvent -> navigationManager.navigateUp() OpenLoggerEvent -> repository.openLogger() + SwitchZoomEvent -> onZoomButtonClicked() }.exhaustive } + private fun onZoomButtonClicked() { + (_state.value as? WorkingState)?.let { + _state.value = it.copy(zoomIn = !it.zoomIn) + } + } + private fun disconnect() { repository.release() navigationManager.navigateUp() diff --git a/profile_hrs/src/main/res/drawable/ic_zoom_in.xml b/profile_hrs/src/main/res/drawable/ic_zoom_in.xml new file mode 100644 index 00000000..5e088e1c --- /dev/null +++ b/profile_hrs/src/main/res/drawable/ic_zoom_in.xml @@ -0,0 +1,13 @@ + + + + diff --git a/profile_hrs/src/main/res/drawable/ic_zoom_out.xml b/profile_hrs/src/main/res/drawable/ic_zoom_out.xml new file mode 100644 index 00000000..4e1bf6b0 --- /dev/null +++ b/profile_hrs/src/main/res/drawable/ic_zoom_out.xml @@ -0,0 +1,10 @@ + + + diff --git a/profile_hrs/src/main/res/values/strings.xml b/profile_hrs/src/main/res/values/strings.xml index 6c7eee4d..9d719a2c 100644 --- a/profile_hrs/src/main/res/values/strings.xml +++ b/profile_hrs/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ HRS + Data + Icon to zoom chart in or out