summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-02-24 07:40:37 +0000
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-02-24 07:40:37 +0000
commit8d669755396a58eb3894144b25631ff7577954be (patch)
tree3aea080c86ba2f51c6cb8ad4302765d1ddf5b3a6
parentac0f57662d5c953f73c0561edc11e0ae8c9a0404 (diff)
Update graph UI, #4582
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt194
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/GraphStyle.kt62
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt94
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt23
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt11
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt44
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt39
-rw-r--r--app/src/main/res/drawable/bg_rounded.xml (renamed from app/src/main/res/drawable/ic_disk.xml)10
-rw-r--r--app/src/main/res/drawable/part_square.xml34
-rw-r--r--app/src/main/res/layout/chart_tooltip.xml86
-rw-r--r--app/src/main/res/layout/fragment_dashboard.xml20
-rw-r--r--app/src/main/res/layout/trackers_item_graph.xml2
-rw-r--r--app/src/main/res/values/colors.xml2
-rw-r--r--app/src/main/res/values/strings.xml2
14 files changed, 441 insertions, 182 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
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 <https://www.gnu.org/licenses/>.
+ */
+
+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<Int>()
+ set(value) {
+ field = value
+ refreshDataSet()
+ }
+ var labels = emptyList<String>()
+
+ 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<View>(id)?.let {
+ if (it.isVisible != showIt) {
+ it.isVisible = showIt
+ }
+ }
+ }
+ }
+
+ fun setLabel(label: String?) {
+ findViewById<TextView>(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 <https://www.gnu.org/licenses/>.
- */
-
-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<Int>, 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<Int>,
+ val periods: List<String>,
+ val trackersCount: Int
+)
class TrackersStatisticsUseCase(
private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule
) {
@@ -36,35 +44,66 @@ class TrackersStatisticsUseCase(
awaitClose { trackTrackersPrivacyModule.removeListener(listener) }
}
- fun listenDayStatistics(): Flow<Triple<List<Int>, Int, Int>> = callbackFlow {
- val listener = object : ITrackTrackersPrivacyModule.Listener {
- override fun onNewData() {
- offer(getDayStatistics())
- }
+ fun getDayStatistics(): Pair<TrackersPeriodicStatistics, Int> {
+ return TrackersPeriodicStatistics(
+ calls = trackTrackersPrivacyModule.getPastDayTrackersCalls(),
+ periods = buildDayLabels(),
+ trackersCount = trackTrackersPrivacyModule.getPastDayTrackersCount()
+ ) to trackTrackersPrivacyModule.getTrackersCount()
+ }
+
+ private fun buildDayLabels(): List<String> {
+ val formater = DateTimeFormatter.ofPattern("HH:mm")
+ 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)}")
+ end = start.minus(1, ChronoUnit.MINUTES)
}
+ return periods.reversed()
+ }
- offer(getDayStatistics())
- trackTrackersPrivacyModule.addListener(listener)
- awaitClose { trackTrackersPrivacyModule.removeListener(listener) }
+ private fun buildMonthLabels(): List<String> {
+ val formater = DateTimeFormatter.ofPattern("MMM d - EEE")
+ val periods = mutableListOf<String>()
+ 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<List<Int>, Int, Int> {
- return Triple(
- trackTrackersPrivacyModule.getPastDayTrackersCalls(),
- trackTrackersPrivacyModule.getPastDayTrackersCount(),
- trackTrackersPrivacyModule.getTrackersCount()
- )
+ private fun buildYearLabels(): List<String> {
+ val formater = DateTimeFormatter.ofPattern("MMM yyyy")
+ val periods = mutableListOf<String>()
+ 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<List<Int>, Int>,
- Pair<List<Int>, Int>,
- Pair<List<Int>, Int>> {
+ fun getDayMonthYearStatistics(): Triple<TrackersPeriodicStatistics, TrackersPeriodicStatistics, TrackersPeriodicStatistics> {
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<Tracker> {
return trackTrackersPrivacyModule.getTrackersForApp(appUid)
}
-
- private fun List<Int>.pruneEmptyHistoric(): List<Int> {
- val result = mutableListOf<Int>()
- 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<Int>? = null
+ val dayStatistics: List<Int>? = null,
+ val dayLabels: List<String>? = null
)
sealed class SingleEvent {
@@ -84,6 +85,7 @@ class DashboardFeature(
data class IpScramblingModeUpdatedEffect(val mode: InternetPrivacyMode) : Effect()
data class TrackersStatisticsUpdatedEffect(
val dayStatistics: List<Int>,
+ val dayLabels: List<String>,
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<Effect>(
// 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<Effect>(
// 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<Int>? = null,
- val dayTrackersCount: Int? = null,
- val monthStatistics: List<Int>? = null,
- val monthTrackersCount: Int? = null,
- val yearStatistics: List<Int>? = null,
- val yearTrackersCount: Int? = null,
+ val dayStatistics: TrackersPeriodicStatistics? = null,
+ val monthStatistics: TrackersPeriodicStatistics? = null,
+ val yearStatistics: TrackersPeriodicStatistics? = null,
val apps: List<ApplicationDescription>? = null,
-
- val trackers: List<Tracker> = emptyList(),
- val currentSelectedTracker: Tracker? = null
+ val trackers: List<Tracker> = emptyList()
)
sealed class SingleEvent {
@@ -75,12 +71,9 @@ class TrackersFeature(
sealed class Effect {
data class TrackersStatisticsLoadedEffect(
- val dayStatistics: List<Int>? = null,
- val dayTrackersCount: Int? = null,
- val monthStatistics: List<Int>? = null,
- val monthTrackersCount: Int? = null,
- val yearStatistics: List<Int>? = null,
- val yearTrackersCount: Int? = null
+ val dayStatistics: TrackersPeriodicStatistics? = null,
+ val monthStatistics: TrackersPeriodicStatistics? = null,
+ val yearStatistics: TrackersPeriodicStatistics? = null
) : Effect()
data class AvailableAppsListEffect(
val apps: List<ApplicationDescription>
@@ -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<Int>, 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<TrackersFeature.Action> = viewModel.actions
diff --git a/app/src/main/res/drawable/ic_disk.xml b/app/src/main/res/drawable/bg_rounded.xml
index 2600601..0677165 100644
--- a/app/src/main/res/drawable/ic_disk.xml
+++ b/app/src/main/res/drawable/bg_rounded.xml
@@ -15,7 +15,9 @@
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid android:color="@color/e_blue2" />
-</shape> \ No newline at end of file
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <solid android:color="@color/dark_color" />
+ <corners android:radius="3dp" />
+</shape>
diff --git a/app/src/main/res/drawable/part_square.xml b/app/src/main/res/drawable/part_square.xml
new file mode 100644
index 0000000..6b1ae23
--- /dev/null
+++ b/app/src/main/res/drawable/part_square.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:width="12dp" android:height="12dp">
+ <rotate
+ android:fromDegrees="45"
+ android:toDegrees="45"
+ android:pivotX="50%"
+ android:pivotY="50%" >
+ <shape
+ android:shape="rectangle" >
+ <stroke android:color="#00FFFFFF" android:width="4dp"/>
+ <corners android:radius="1dp" />
+ <solid
+ android:color="@color/dark_color" />
+ </shape>
+ </rotate>
+ </item>
+</layer-list>
diff --git a/app/src/main/res/layout/chart_tooltip.xml b/app/src/main/res/layout/chart_tooltip.xml
new file mode 100644
index 0000000..a88bc6f
--- /dev/null
+++ b/app/src/main/res/layout/chart_tooltip.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="44dp"
+ >
+ <View
+ android:id="@+id/arrow_top_left"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:layout_gravity="top|left"
+ android:layout_marginLeft="4dp"
+ android:background="@drawable/part_square"
+ android:visibility="gone"
+ />
+ <View
+ android:id="@+id/arrow_top_center"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:layout_gravity="top|center_horizontal"
+ android:background="@drawable/part_square"
+ android:visibility="gone"
+ />
+ <View
+ android:id="@+id/arrow_top_right"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:layout_gravity="top|right"
+ android:layout_marginRight="4dp"
+ android:background="@drawable/part_square"
+ android:visibility="gone"
+ />
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="01/00 - 01:59"
+ android:textColor="@color/white"
+ android:paddingHorizontal="8dp"
+ android:background="@drawable/bg_rounded"
+ android:layout_marginTop="6dp"
+ android:layout_marginBottom="6dp"
+ />
+ <View
+ android:id="@+id/arrow_bottom_left"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:layout_gravity="bottom|left"
+ android:layout_marginLeft="4dp"
+ android:background="@drawable/part_square"
+ android:visibility="gone"
+ />
+ <View
+ android:id="@+id/arrow_bottom_center"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:layout_gravity="bottom|center_horizontal"
+ android:background="@drawable/part_square"
+ android:visibility="gone"
+ />
+ <View
+ android:id="@+id/arrow_bottom_right"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:layout_gravity="bottom|right"
+ android:layout_marginRight="4dp"
+ android:background="@drawable/part_square"
+ android:visibility="gone"
+ />
+</FrameLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
index 66c738c..93c1d36 100644
--- a/app/src/main/res/layout/fragment_dashboard.xml
+++ b/app/src/main/res/layout/fragment_dashboard.xml
@@ -178,32 +178,22 @@ android:text="@string/dashboard_state_ipaddress_off"
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/graph"
- android:layout_height="100dp"
+ android:layout_height="144dp"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="@+id/graph_period"
android:layout_marginTop="16dp"
/>
- <View
- android:id="@+id/graph_legend_form"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:padding="1dp"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/graph"
- android:layout_marginTop="8dp"
- android:layout_marginStart="16dp"
- android:background="@drawable/ic_disk"
- />
<TextView
android:id="@+id/graph_legend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="0 Trackers"
- android:layout_marginStart="8dp"
android:textSize="12sp"
- app:layout_constraintLeft_toRightOf="@+id/graph_legend_form"
- app:layout_constraintBottom_toBottomOf="@+id/graph_legend_form"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="16dp"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/graph"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/trackers_item_graph.xml b/app/src/main/res/layout/trackers_item_graph.xml
index d0bd9ec..e7245f9 100644
--- a/app/src/main/res/layout/trackers_item_graph.xml
+++ b/app/src/main/res/layout/trackers_item_graph.xml
@@ -51,7 +51,7 @@
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/graph"
- android:layout_height="72dp"
+ android:layout_height="110dp"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="@+id/graph_period_label"
/>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index b590972..befe02b 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -24,4 +24,6 @@
<color name="e_blue2">@lineageos.platform:color/color_default_blue1</color>
+ <color name="dark_color">#263238</color>
+ <color name="blue_unselected">#AADCFE</color>
</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f484dd7..255f04a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -17,7 +17,7 @@
<string name="dashboard_state_ipaddress_off">Exposed</string>
<string name="dashboard_state_ipaddress_on">Hidden</string>
<string name="dashboard_graph_label">Personal data leakage</string>
- <string name="dashboard_graph_period">Last 24 hours</string>
+ <string name="dashboard_graph_period">Today</string>
<string name="dashboard_graph_trackers_legend">%s Trackers</string>
<string name="dashboard_am_i_tracked_title">Manage trackers</string>