From 8d669755396a58eb3894144b25631ff7577954be Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Thu, 24 Feb 2022 07:40:37 +0000 Subject: Update graph UI, #4582 --- .../e/privacycentralapp/common/GraphHolder.kt | 194 +++++++++++++++++++++ .../e/privacycentralapp/common/GraphStyle.kt | 62 ------- .../domain/usecases/TrackersStatisticsUseCase.kt | 94 ++++++---- .../features/dashboard/DashboardFeature.kt | 23 ++- .../features/dashboard/DashboardFragment.kt | 11 +- .../features/trackers/TrackersFeature.kt | 44 ++--- .../features/trackers/TrackersFragment.kt | 39 ++--- 7 files changed, 306 insertions(+), 161 deletions(-) create mode 100644 app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt delete mode 100644 app/src/main/java/foundation/e/privacycentralapp/common/GraphStyle.kt (limited to 'app/src/main/java/foundation/e/privacycentralapp') diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt b/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt new file mode 100644 index 0000000..db6bc7e --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacycentralapp.common + +import android.content.Context +import android.graphics.Canvas +import android.view.View +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.MPPointF +import foundation.e.privacycentralapp.R + +class GraphHolder(val barChart: BarChart, val context: Context, val isMarkerAbove: Boolean = true) { + var data = emptyList() + set(value) { + field = value + refreshDataSet() + } + var labels = emptyList() + + private var isHighlighted = false + + init { + barChart.apply { + description = null + setTouchEnabled(true) + setScaleEnabled(false) + + setDrawGridBackground(false) + setDrawBorders(false) + axisLeft.isEnabled = false + axisRight.isEnabled = false + + legend.isEnabled = false + + if (isMarkerAbove) { + extraTopOffset = 44f + } else { + extraBottomOffset = 44f + } + + offsetTopAndBottom(0) + xAxis.apply { + isEnabled = true + position = XAxis.XAxisPosition.BOTH_SIDED + setDrawGridLines(false) + setDrawLabels(false) + setDrawValueAboveBar(false) + } + + val periodMarker = PeriodMarkerView(context, isMarkerAbove) + periodMarker.chartView = this + marker = periodMarker + + setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(e: Entry?, h: Highlight?) { + h?.let { periodMarker.setLabel(labels.getOrNull(it.x.toInt())) } + isHighlighted = true + refreshDataSet() + } + + override fun onNothingSelected() { + isHighlighted = false + refreshDataSet() + } + }) + } + } + + private fun refreshDataSet() { + val trackersDataSet = BarDataSet( + data.mapIndexed { index, value -> BarEntry(index.toFloat(), value.toFloat()) }, + "" + ).apply { + color = ContextCompat.getColor( + context, + if (isHighlighted) R.color.blue_unselected else R.color.accent + ) + setDrawValues(false) + highLightColor = ContextCompat.getColor( + context, R.color.accent + ) + highLightAlpha = 255 + } + + barChart.data = BarData(trackersDataSet) + barChart.invalidate() + } +} + +private fun Int.dpToPxF(context: Context): Float = this.toFloat() * context.resources.displayMetrics.density + +class PeriodMarkerView(context: Context, private val isMarkerAbove: Boolean = true) : MarkerView(context, R.layout.chart_tooltip) { + enum class ArrowPosition { LEFT, CENTER, RIGHT } + + private val arrowMargins = 10.dpToPxF(context) + private val mOffset2 = MPPointF(0f, 0f) + + private fun getArrowPosition(posX: Float): ArrowPosition { + val halfWidth = width / 2 + + return chartView?.let { chart -> + if (posX < halfWidth) { + ArrowPosition.LEFT + } else if (chart.width - posX < halfWidth) { + ArrowPosition.RIGHT + } else { + ArrowPosition.CENTER + } + } ?: ArrowPosition.CENTER + } + + private fun showArrow(position: ArrowPosition?) { + val ids = listOf( + R.id.arrow_top_left, R.id.arrow_top_center, R.id.arrow_top_right, + R.id.arrow_bottom_left, R.id.arrow_bottom_center, R.id.arrow_bottom_right + ) + + val toShow = if (isMarkerAbove) when (position) { + ArrowPosition.LEFT -> R.id.arrow_bottom_left + ArrowPosition.CENTER -> R.id.arrow_bottom_center + ArrowPosition.RIGHT -> R.id.arrow_bottom_right + else -> null + } else when (position) { + ArrowPosition.LEFT -> R.id.arrow_top_left + ArrowPosition.CENTER -> R.id.arrow_top_center + ArrowPosition.RIGHT -> R.id.arrow_top_right + else -> null + } + + ids.forEach { id -> + val showIt = id == toShow + findViewById(id)?.let { + if (it.isVisible != showIt) { + it.isVisible = showIt + } + } + } + } + + fun setLabel(label: String?) { + findViewById(R.id.label).text = label + } + + override fun refreshContent(e: Entry?, highlight: Highlight?) { + highlight?.let { + showArrow(getArrowPosition(highlight.xPx)) + } + super.refreshContent(e, highlight) + } + + override fun getOffsetForDrawingAtPoint(posX: Float, posY: Float): MPPointF { + val x = when (getArrowPosition(posX)) { + ArrowPosition.LEFT -> -arrowMargins + ArrowPosition.RIGHT -> -width + arrowMargins + ArrowPosition.CENTER -> -width.toFloat() / 2 + } + + mOffset2.x = x + mOffset2.y = if (isMarkerAbove) -posY + else -posY + (chartView?.height?.toFloat() ?: 0f) - height + + return mOffset2 + } + + override fun draw(canvas: Canvas?, posX: Float, posY: Float) { + super.draw(canvas, posX, posY) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/GraphStyle.kt b/app/src/main/java/foundation/e/privacycentralapp/common/GraphStyle.kt deleted file mode 100644 index 63a0f3f..0000000 --- a/app/src/main/java/foundation/e/privacycentralapp/common/GraphStyle.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.privacycentralapp.common - -import androidx.annotation.ColorInt -import com.github.mikephil.charting.charts.BarChart -import com.github.mikephil.charting.components.XAxis -import com.github.mikephil.charting.data.BarData -import com.github.mikephil.charting.data.BarDataSet -import com.github.mikephil.charting.data.BarEntry - -fun customizeBarChart(barChart: BarChart) { - barChart.apply { - description = null - setTouchEnabled(false) - setDrawGridBackground(false) - setDrawBorders(false) - axisLeft.isEnabled = false - axisRight.isEnabled = false - - legend.isEnabled = false - - xAxis.apply { - isEnabled = true - position = XAxis.XAxisPosition.BOTH_SIDED - setDrawGridLines(false) - yOffset = 32f - setDrawLabels(false) - // setDrawLimitLinesBehindData(true) - setDrawValueAboveBar(false) - } - } -} - -fun updateGraphData(values: List, graph: BarChart, @ColorInt graphColor: Int) { - - val trackersDataSet = BarDataSet( - values.mapIndexed { index, value -> BarEntry(index.toFloat(), value.toFloat()) }, - "" - ).apply { - color = graphColor - setDrawValues(false) - } - - graph.data = BarData(trackersDataSet) - graph.invalidate() -} diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt index 2ed1077..1e1728c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt @@ -22,7 +22,15 @@ import foundation.e.privacymodules.trackers.Tracker import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +data class TrackersPeriodicStatistics( + val calls: List, + val periods: List, + val trackersCount: Int +) class TrackersStatisticsUseCase( private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule ) { @@ -36,35 +44,66 @@ class TrackersStatisticsUseCase( awaitClose { trackTrackersPrivacyModule.removeListener(listener) } } - fun listenDayStatistics(): Flow, Int, Int>> = callbackFlow { - val listener = object : ITrackTrackersPrivacyModule.Listener { - override fun onNewData() { - offer(getDayStatistics()) - } + fun getDayStatistics(): Pair { + return TrackersPeriodicStatistics( + calls = trackTrackersPrivacyModule.getPastDayTrackersCalls(), + periods = buildDayLabels(), + trackersCount = trackTrackersPrivacyModule.getPastDayTrackersCount() + ) to trackTrackersPrivacyModule.getTrackersCount() + } + + private fun buildDayLabels(): List { + val formater = DateTimeFormatter.ofPattern("HH:mm") + val periods = mutableListOf() + var end = ZonedDateTime.now() + for (i in 1..24) { + val start = end.truncatedTo(ChronoUnit.HOURS) + periods.add("${formater.format(start)} - ${formater.format(end)}") + end = start.minus(1, ChronoUnit.MINUTES) } + return periods.reversed() + } - offer(getDayStatistics()) - trackTrackersPrivacyModule.addListener(listener) - awaitClose { trackTrackersPrivacyModule.removeListener(listener) } + private fun buildMonthLabels(): List { + val formater = DateTimeFormatter.ofPattern("MMM d - EEE") + val periods = mutableListOf() + var day = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS) + for (i in 1..30) { + periods.add(formater.format(day)) + day = day.minus(1, ChronoUnit.DAYS) + } + return periods.reversed() } - fun getDayStatistics(): Triple, Int, Int> { - return Triple( - trackTrackersPrivacyModule.getPastDayTrackersCalls(), - trackTrackersPrivacyModule.getPastDayTrackersCount(), - trackTrackersPrivacyModule.getTrackersCount() - ) + private fun buildYearLabels(): List { + val formater = DateTimeFormatter.ofPattern("MMM yyyy") + val periods = mutableListOf() + var month = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1) + for (i in 1..12) { + periods.add(formater.format(month)) + month = month.minus(1, ChronoUnit.MONTHS) + } + return periods.reversed() } - fun getDayMonthYearStatistics(): Triple< - Pair, Int>, - Pair, Int>, - Pair, Int>> { + fun getDayMonthYearStatistics(): Triple { return with(trackTrackersPrivacyModule) { Triple( - getPastDayTrackersCalls() to getPastDayTrackersCount(), - getPastMonthTrackersCalls() to getPastMonthTrackersCount(), - getPastYearTrackersCalls() to getPastYearTrackersCount() + TrackersPeriodicStatistics( + calls = getPastDayTrackersCalls(), + periods = buildDayLabels(), + trackersCount = getPastDayTrackersCount() + ), + TrackersPeriodicStatistics( + calls = getPastMonthTrackersCalls(), + periods = buildMonthLabels(), + trackersCount = getPastMonthTrackersCount() + ), + TrackersPeriodicStatistics( + calls = getPastYearTrackersCalls(), + periods = buildYearLabels(), + trackersCount = getPastYearTrackersCount() + ) ) } } @@ -72,17 +111,4 @@ class TrackersStatisticsUseCase( fun getTrackers(appUid: Int): List { return trackTrackersPrivacyModule.getTrackersForApp(appUid) } - - private fun List.pruneEmptyHistoric(): List { - val result = mutableListOf() - reversed().forEach { - if (result.isNotEmpty() || it != 0) { - result.add(it) - } - } - if (result.isEmpty() && !isEmpty()) { - result.add(last()) - } - return result - } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt index 5a37246..b434bb4 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt @@ -57,7 +57,8 @@ class DashboardFeature( // val graphData val trackersCount: Int? = null, val activeTrackersCount: Int? = null, - val dayStatistics: List? = null + val dayStatistics: List? = null, + val dayLabels: List? = null ) sealed class SingleEvent { @@ -84,6 +85,7 @@ class DashboardFeature( data class IpScramblingModeUpdatedEffect(val mode: InternetPrivacyMode) : Effect() data class TrackersStatisticsUpdatedEffect( val dayStatistics: List, + val dayLabels: List, val dayTrackersCount: Int, val trackersCount: Int ) : Effect() @@ -114,6 +116,7 @@ class DashboardFeature( is Effect.IpScramblingModeUpdatedEffect -> state.copy(internetPrivacyMode = effect.mode) is Effect.TrackersStatisticsUpdatedEffect -> state.copy( dayStatistics = effect.dayStatistics, + dayLabels = effect.dayLabels, activeTrackersCount = effect.dayTrackersCount, trackersCount = effect.trackersCount ) @@ -147,11 +150,12 @@ class DashboardFeature( flowOf( // trackersStatisticsUseCase.listenDayStatistics().map { trackersStatisticsUseCase.getDayStatistics().let { - (dayStatistics, dayTrackersCount, trackersCount) -> + (dayStatistics, trackersCount) -> Effect.TrackersStatisticsUpdatedEffect( - dayStatistics, - dayTrackersCount, - trackersCount + dayStatistics = dayStatistics.calls, + dayLabels = dayStatistics.periods, + dayTrackersCount = dayStatistics.trackersCount, + trackersCount = trackersCount ) } ), @@ -171,11 +175,12 @@ class DashboardFeature( Action.FetchStatistics -> flowOf( // trackersStatisticsUseCase.listenDayStatistics().map { trackersStatisticsUseCase.getDayStatistics().let { - (dayStatistics, dayTrackersCount, trackersCount) -> + (dayStatistics, trackersCount) -> Effect.TrackersStatisticsUpdatedEffect( - dayStatistics, - dayTrackersCount, - trackersCount + dayStatistics = dayStatistics.calls, + dayLabels = dayStatistics.periods, + dayTrackersCount = dayStatistics.trackersCount, + trackersCount = trackersCount ) } ) diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt index 441f3d6..e60243d 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt @@ -29,9 +29,8 @@ import foundation.e.flowmvi.MVIView import foundation.e.privacycentralapp.DependencyContainer import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.common.GraphHolder import foundation.e.privacycentralapp.common.NavToolbarFragment -import foundation.e.privacycentralapp.common.customizeBarChart -import foundation.e.privacycentralapp.common.updateGraphData import foundation.e.privacycentralapp.databinding.FragmentDashboardBinding import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode import foundation.e.privacycentralapp.domain.entities.LocationMode @@ -55,6 +54,7 @@ class DashboardFragment : viewModelProviderFactoryOf { dependencyContainer.dashBoardViewModelFactory.create() } } + private lateinit var graphHolder: GraphHolder private lateinit var binding: FragmentDashboardBinding override fun onCreate(savedInstanceState: Bundle?) { @@ -105,7 +105,7 @@ class DashboardFragment : super.onViewCreated(view, savedInstanceState) binding = FragmentDashboardBinding.bind(view) - customizeBarChart(binding.graph) + graphHolder = GraphHolder(binding.graph, requireContext()) binding.togglePrivacyCentral.setOnClickListener { viewModel.submitAction(DashboardFeature.Action.TogglePrivacyAction) @@ -205,9 +205,8 @@ class DashboardFragment : ) ) - state.dayStatistics?.let { - updateGraphData(it, binding.graph, getColor(requireContext(), R.color.e_blue2)) - } + state.dayStatistics?.let { graphHolder.data = it } + state.dayLabels?.let { graphHolder.labels = it } binding.graphLegend.text = getString(R.string.dashboard_graph_trackers_legend, state.activeTrackersCount?.toString() ?: "No") diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt index d061c4a..f38e50f 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt @@ -24,6 +24,7 @@ import foundation.e.flowmvi.SingleEventProducer import foundation.e.flowmvi.feature.BaseFeature import foundation.e.privacycentralapp.domain.usecases.AppListUseCase import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase +import foundation.e.privacycentralapp.domain.usecases.TrackersPeriodicStatistics import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import foundation.e.privacymodules.permissions.data.ApplicationDescription import foundation.e.privacymodules.trackers.Tracker @@ -49,16 +50,11 @@ class TrackersFeature( singleEventProducer ) { data class State( - val dayStatistics: List? = null, - val dayTrackersCount: Int? = null, - val monthStatistics: List? = null, - val monthTrackersCount: Int? = null, - val yearStatistics: List? = null, - val yearTrackersCount: Int? = null, + val dayStatistics: TrackersPeriodicStatistics? = null, + val monthStatistics: TrackersPeriodicStatistics? = null, + val yearStatistics: TrackersPeriodicStatistics? = null, val apps: List? = null, - - val trackers: List = emptyList(), - val currentSelectedTracker: Tracker? = null + val trackers: List = emptyList() ) sealed class SingleEvent { @@ -75,12 +71,9 @@ class TrackersFeature( sealed class Effect { data class TrackersStatisticsLoadedEffect( - val dayStatistics: List? = null, - val dayTrackersCount: Int? = null, - val monthStatistics: List? = null, - val monthTrackersCount: Int? = null, - val yearStatistics: List? = null, - val yearTrackersCount: Int? = null + val dayStatistics: TrackersPeriodicStatistics? = null, + val monthStatistics: TrackersPeriodicStatistics? = null, + val yearStatistics: TrackersPeriodicStatistics? = null ) : Effect() data class AvailableAppsListEffect( val apps: List @@ -104,11 +97,8 @@ class TrackersFeature( when (effect) { is Effect.TrackersStatisticsLoadedEffect -> state.copy( dayStatistics = effect.dayStatistics, - dayTrackersCount = effect.dayTrackersCount, monthStatistics = effect.monthStatistics, - monthTrackersCount = effect.monthTrackersCount, yearStatistics = effect.yearStatistics, - yearTrackersCount = effect.yearTrackersCount ) is Effect.AvailableAppsListEffect -> state.copy(apps = effect.apps) @@ -124,12 +114,9 @@ class TrackersFeature( .let { (day, month, year) -> emit( Effect.TrackersStatisticsLoadedEffect( - dayStatistics = day.first, - dayTrackersCount = day.second, - monthStatistics = month.first, - monthTrackersCount = month.second, - yearStatistics = year.first, - yearTrackersCount = year.second + dayStatistics = day, + monthStatistics = month, + yearStatistics = year ) ) } @@ -154,12 +141,9 @@ class TrackersFeature( .let { (day, month, year) -> emit( Effect.TrackersStatisticsLoadedEffect( - dayStatistics = day.first, - dayTrackersCount = day.second, - monthStatistics = month.first, - monthTrackersCount = month.second, - yearStatistics = year.first, - yearTrackersCount = year.second + dayStatistics = day, + monthStatistics = month, + yearStatistics = year, ) ) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt index c017abd..088787c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt @@ -20,7 +20,6 @@ package foundation.e.privacycentralapp.features.trackers import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.core.content.ContextCompat import androidx.fragment.app.add import androidx.fragment.app.commit import androidx.fragment.app.viewModels @@ -31,11 +30,11 @@ import foundation.e.privacycentralapp.DependencyContainer import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.AppsAdapter +import foundation.e.privacycentralapp.common.GraphHolder import foundation.e.privacycentralapp.common.NavToolbarFragment -import foundation.e.privacycentralapp.common.customizeBarChart -import foundation.e.privacycentralapp.common.updateGraphData import foundation.e.privacycentralapp.databinding.FragmentTrackersBinding import foundation.e.privacycentralapp.databinding.TrackersItemGraphBinding +import foundation.e.privacycentralapp.domain.usecases.TrackersPeriodicStatistics import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFragment import kotlinx.coroutines.flow.Flow @@ -54,6 +53,9 @@ class TrackersFragment : } private lateinit var binding: FragmentTrackersBinding + private lateinit var dayGraphHolder: GraphHolder + private lateinit var monthGraphHolder: GraphHolder + private lateinit var yearGraphHolder: GraphHolder override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -95,9 +97,9 @@ class TrackersFragment : binding = FragmentTrackersBinding.bind(view) - listOf(binding.graphDay, binding.graphMonth, binding.graphYear).forEach { - customizeBarChart(it.graph) - } + dayGraphHolder = GraphHolder(binding.graphDay.graph, requireContext(), false) + monthGraphHolder = GraphHolder(binding.graphMonth.graph, requireContext(), false) + yearGraphHolder = GraphHolder(binding.graphYear.graph, requireContext(), false) binding.apps.apply { layoutManager = LinearLayoutManager(requireContext()) @@ -118,17 +120,9 @@ class TrackersFragment : override fun getTitle() = getString(R.string.trackers_title) override fun render(state: TrackersFeature.State) { - if (state.dayStatistics != null && state.dayTrackersCount != null) { - renderGraph(state.dayTrackersCount, state.dayStatistics, binding.graphDay) - } - - if (state.monthStatistics != null && state.monthTrackersCount != null) { - renderGraph(state.monthTrackersCount, state.monthStatistics, binding.graphMonth) - } - - if (state.yearStatistics != null && state.yearTrackersCount != null) { - renderGraph(state.yearTrackersCount, state.yearStatistics, binding.graphYear) - } + state.dayStatistics?.let { renderGraph(it, dayGraphHolder, binding.graphDay) } + state.monthStatistics?.let { renderGraph(it, monthGraphHolder, binding.graphMonth) } + state.yearStatistics?.let { renderGraph(it, yearGraphHolder, binding.graphYear) } state.apps?.let { binding.apps.post { @@ -137,9 +131,14 @@ class TrackersFragment : } } - private fun renderGraph(trackersCount: Int, data: List, graphBinding: TrackersItemGraphBinding) { - updateGraphData(data, graphBinding.graph, ContextCompat.getColor(requireContext(), R.color.e_blue2)) - graphBinding.trackersCountLabel.text = getString(R.string.trackers_count_label, trackersCount) + private fun renderGraph( + statistics: TrackersPeriodicStatistics, + graphHolder: GraphHolder, + graphBinding: TrackersItemGraphBinding + ) { + graphHolder.data = statistics.calls + graphHolder.labels = statistics.periods + graphBinding.trackersCountLabel.text = getString(R.string.trackers_count_label, statistics.trackersCount) } override fun actions(): Flow = viewModel.actions -- cgit v1.2.1