summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-12-27 08:43:14 +0100
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-12-27 08:43:14 +0100
commit3e353baf484524663b21f6a0cbb232634595fc33 (patch)
tree2c4cd1272cde86c9b52e3801bcc3a1677af85b40 /app/src
parentae2feae022ba6485b020afa6b5f5c78049ecf1d4 (diff)
5422: add time graduations on tracker graph on dashboard.
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt171
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackersPeriodicStatistics.kt3
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt22
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt1
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt1
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt1
6 files changed, 154 insertions, 45 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt b/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt
index d7a9dd0..5622806 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt
@@ -29,15 +29,19 @@ import androidx.core.content.ContextCompat
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import com.github.mikephil.charting.charts.BarChart
+import com.github.mikephil.charting.components.AxisBase
import com.github.mikephil.charting.components.MarkerView
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.components.YAxis
+import com.github.mikephil.charting.components.YAxis.AxisDependency
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.formatter.ValueFormatter
import com.github.mikephil.charting.highlight.Highlight
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
+import com.github.mikephil.charting.renderer.XAxisRenderer
import com.github.mikephil.charting.utils.MPPointF
import foundation.e.privacycentralapp.R
import foundation.e.privacycentralapp.common.extensions.dpToPxF
@@ -50,60 +54,145 @@ class GraphHolder(val barChart: BarChart, val context: Context, val isMarkerAbov
}
var labels = emptyList<String>()
+ var graduations: List<String?>? = null
+
private var isHighlighted = false
init {
- barChart.apply {
- description = null
- setTouchEnabled(true)
- setScaleEnabled(false)
+ barChart.description = null
+ barChart.setTouchEnabled(true)
+ barChart.setScaleEnabled(false)
+
+ barChart.setDrawGridBackground(false)
+ barChart.setDrawBorders(false)
+ barChart.axisLeft.isEnabled = false
+ barChart.axisRight.isEnabled = false
+
+ barChart.legend.isEnabled = false
+
+ if (isMarkerAbove) prepareXAxisDashboardDay() else prepareXAxisMarkersBelow()
+
+ val periodMarker = PeriodMarkerView(context, isMarkerAbove)
+ periodMarker.chartView = barChart
+ barChart.marker = periodMarker
+
+ barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
+ override fun onValueSelected(e: Entry?, h: Highlight?) {
+ h?.let {
+ val index = it.x.toInt()
+ if (index >= 0 &&
+ index < labels.size &&
+ index < this@GraphHolder.data.size
+ ) {
+ val period = labels[index]
+ val (blocked, leaked) = this@GraphHolder.data[index]
+ periodMarker.setLabel(period, blocked, leaked)
+ }
+ }
+ isHighlighted = true
+ }
- setDrawGridBackground(false)
- setDrawBorders(false)
- axisLeft.isEnabled = false
- axisRight.isEnabled = false
+ override fun onNothingSelected() {
+ isHighlighted = false
+ }
+ })
+ }
- legend.isEnabled = false
+ private fun prepareXAxisDashboardDay() {
+ barChart.extraTopOffset = 44f
- if (isMarkerAbove) {
- extraTopOffset = 44f
- } else {
- extraBottomOffset = 44f
- }
+ barChart.offsetTopAndBottom(0)
+
+ barChart.setXAxisRenderer(object : XAxisRenderer(barChart.viewPortHandler, barChart.xAxis, barChart.getTransformer(AxisDependency.LEFT)) {
+ override fun renderAxisLine(c: Canvas) {
+ mAxisLinePaint.color = mXAxis.axisLineColor
+ mAxisLinePaint.strokeWidth = mXAxis.axisLineWidth
+ mAxisLinePaint.pathEffect = mXAxis.axisLineDashPathEffect
+
+ // Top line
+ c.drawLine(
+ mViewPortHandler.contentLeft(),
+ mViewPortHandler.contentTop(), mViewPortHandler.contentRight(),
+ mViewPortHandler.contentTop(), mAxisLinePaint
+ )
- offsetTopAndBottom(0)
- xAxis.apply {
- isEnabled = true
- position = XAxis.XAxisPosition.BOTH_SIDED
- setDrawGridLines(false)
- setDrawLabels(false)
- setDrawValueAboveBar(false)
+ // Bottom line
+ c.drawLine(
+ mViewPortHandler.contentLeft(),
+ mViewPortHandler.contentBottom() - 7.dpToPxF(context),
+ mViewPortHandler.contentRight(),
+ mViewPortHandler.contentBottom() - 7.dpToPxF(context),
+ mAxisLinePaint
+ )
}
- val periodMarker = PeriodMarkerView(context, isMarkerAbove)
- periodMarker.chartView = this
- marker = periodMarker
-
- setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
- override fun onValueSelected(e: Entry?, h: Highlight?) {
- h?.let {
- val index = it.x.toInt()
- if (index >= 0 &&
- index < labels.size &&
- index < this@GraphHolder.data.size
- ) {
- val period = labels[index]
- val (blocked, leaked) = this@GraphHolder.data[index]
- periodMarker.setLabel(period, blocked, leaked)
- }
+ override fun renderGridLines(c: Canvas) {
+ if (!mXAxis.isDrawGridLinesEnabled || !mXAxis.isEnabled) return
+ val clipRestoreCount = c.save()
+ c.clipRect(gridClippingRect)
+ if (mRenderGridLinesBuffer.size != mAxis.mEntryCount * 2) {
+ mRenderGridLinesBuffer = FloatArray(mXAxis.mEntryCount * 2)
+ }
+ val positions = mRenderGridLinesBuffer
+ run {
+ var i = 0
+ while (i < positions.size) {
+ positions[i] = mXAxis.mEntries[i / 2]
+ positions[i + 1] = mXAxis.mEntries[i / 2]
+ i += 2
}
- isHighlighted = true
}
- override fun onNothingSelected() {
- isHighlighted = false
+ mTrans.pointValuesToPixel(positions)
+ setupGridPaint()
+ val gridLinePath = mRenderGridLinesPath
+ gridLinePath.reset()
+ var i = 0
+ while (i < positions.size) {
+ val bottomY = if (graduations?.getOrNull(i / 2) != null) 0 else 3
+ val x = positions[i]
+ gridLinePath.moveTo(x, mViewPortHandler.contentBottom() - 7.dpToPxF(context))
+ gridLinePath.lineTo(x, mViewPortHandler.contentBottom() - bottomY.dpToPxF(context))
+
+ c.drawPath(gridLinePath, mGridPaint)
+
+ gridLinePath.reset()
+
+ i += 2
+ }
+ c.restoreToCount(clipRestoreCount)
+ }
+ })
+
+ barChart.setDrawValueAboveBar(false)
+ barChart.xAxis.apply {
+ isEnabled = true
+ position = XAxis.XAxisPosition.BOTTOM
+
+ setDrawGridLines(true)
+ setDrawLabels(true)
+ setCenterAxisLabels(false)
+ setLabelCount(25, true)
+
+ valueFormatter = object : ValueFormatter() {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String {
+ return graduations?.getOrNull(value.toInt() + 1) ?: ""
}
- })
+ }
+ }
+ }
+
+ private fun prepareXAxisMarkersBelow() {
+ barChart.extraBottomOffset = 44f
+
+ barChart.offsetTopAndBottom(0)
+ barChart.setDrawValueAboveBar(false)
+
+ barChart.xAxis.apply {
+ isEnabled = true
+ position = XAxis.XAxisPosition.BOTH_SIDED
+ setDrawGridLines(false)
+ setDrawLabels(false)
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackersPeriodicStatistics.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackersPeriodicStatistics.kt
index b3a6ade..8ce55dd 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackersPeriodicStatistics.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/TrackersPeriodicStatistics.kt
@@ -20,5 +20,6 @@ package foundation.e.privacycentralapp.domain.entities
data class TrackersPeriodicStatistics(
val callsBlockedNLeaked: List<Pair<Int, Int>>,
val periods: List<String>,
- val trackersCount: Int
+ val trackersCount: Int,
+ val graduations: List<String?>? = null
)
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 57ab1a4..5103eb2 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
@@ -69,7 +69,8 @@ class TrackersStatisticsUseCase(
return TrackersPeriodicStatistics(
callsBlockedNLeaked = trackTrackersPrivacyModule.getPastDayTrackersCalls(),
periods = buildDayLabels(),
- trackersCount = trackTrackersPrivacyModule.getPastDayTrackersCount()
+ trackersCount = trackTrackersPrivacyModule.getPastDayTrackersCount(),
+ graduations = buildDayGraduations(),
) to trackTrackersPrivacyModule.getTrackersCount()
}
@@ -83,15 +84,30 @@ class TrackersStatisticsUseCase(
fun getDayTrackersCount() = trackTrackersPrivacyModule.getPastDayTrackersCount()
+ private fun buildDayGraduations(): List<String?> {
+ val formatter = DateTimeFormatter.ofPattern(
+ resources.getString(R.string.trackers_graph_hours_period_format)
+ )
+
+ val periods = mutableListOf<String?>()
+ var end = ZonedDateTime.now()
+ for (i in 1..24) {
+ val start = end.truncatedTo(ChronoUnit.HOURS)
+ periods.add(if (start.hour % 6 == 0) formatter.format(start) else null)
+ end = start.minus(1, ChronoUnit.MINUTES)
+ }
+ return periods.reversed()
+ }
+
private fun buildDayLabels(): List<String> {
- val formater = DateTimeFormatter.ofPattern(
+ val formatter = DateTimeFormatter.ofPattern(
resources.getString(R.string.trackers_graph_hours_period_format)
)
val periods = mutableListOf<String>()
var end = ZonedDateTime.now()
for (i in 1..24) {
val start = end.truncatedTo(ChronoUnit.HOURS)
- periods.add("${formater.format(start)} - ${formater.format(end)}")
+ periods.add("${formatter.format(start)} - ${formatter.format(end)}")
end = start.minus(1, ChronoUnit.MINUTES)
}
return periods.reversed()
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 8a0a3d4..0dc24e8 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
@@ -259,6 +259,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) {
binding.graphEmpty.isVisible = false
state.dayStatistics?.let { graphHolder?.data = it }
state.dayLabels?.let { graphHolder?.labels = it }
+ state.dayGraduations?.let { graphHolder?.graduations = it }
binding.graphLegend.text = Html.fromHtml(
getString(
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt
index 937fa22..0e3521d 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt
@@ -33,4 +33,5 @@ data class DashboardState(
val allowedTrackersCount: Int? = null,
val dayStatistics: List<Pair<Int, Int>>? = null,
val dayLabels: List<String>? = null,
+ val dayGraduations: List<String?>? = null,
)
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
index 18b4212..ead01a5 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
@@ -114,6 +114,7 @@ class DashboardViewModel(
s.copy(
dayStatistics = dayStatistics.callsBlockedNLeaked,
dayLabels = dayStatistics.periods,
+ dayGraduations = dayStatistics.graduations,
leakedTrackersCount = dayStatistics.trackersCount,
trackersCount = trackersCount,
allowedTrackersCount = nonBlockedTrackersCount