summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjacquarg <guillaume.jacquart@hoodbrains.com>2021-11-01 21:24:09 +0100
committerjacquarg <guillaume.jacquart@hoodbrains.com>2021-11-01 21:24:09 +0100
commita484bf584f4163c8a0a1260e81d598fdec87ff3b (patch)
treed6895488aafed08ef1c178a3b7713024edc02635
parentb0d9079811b08b95dd623d94c1d4338f28597d4c (diff)
Add trackers UI
-rw-r--r--app/build.gradle2
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt6
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt67
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt2
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt16
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt12
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt16
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt77
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt118
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt28
-rw-r--r--app/src/main/res/layout/fragment_trackers.xml59
-rw-r--r--app/src/main/res/layout/trackers_item_app.xml38
-rw-r--r--app/src/main/res/layout/trackers_item_graph.xml58
-rw-r--r--app/src/main/res/values/strings.xml9
14 files changed, 437 insertions, 71 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 11b211e..12b2e1c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -101,7 +101,7 @@ dependencies {
// include the e specific version of the modules, just for the e flavor
eImplementation project(":privacymodulese")
- implementation 'foundation.e:privacymodule.api:0.2.1'
+ implementation 'foundation.e:privacymodule.api:0.3.1'
implementation 'foundation.e:privacymodule.tor:0.1.0'
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
index ccb0a75..1ba235b 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
@@ -30,6 +30,7 @@ import foundation.e.privacycentralapp.features.dashboard.DashBoardViewModelFacto
import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyViewModelFactory
import foundation.e.privacycentralapp.features.location.FakeLocationViewModelFactory
import foundation.e.privacycentralapp.features.location.LocationApiDelegate
+import foundation.e.privacycentralapp.features.trackers.TrackersViewModelFactory
import foundation.e.privacymodules.ipscrambler.IpScramblerModule
import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
import foundation.e.privacymodules.location.FakeLocation
@@ -83,6 +84,7 @@ class DependencyContainer constructor(val app: Application) {
TrackersStatisticsUseCase(trackTrackersPrivacyModule)
}
+ // ViewModelFactories
val dashBoardViewModelFactory by lazy {
DashBoardViewModelFactory(getQuickPrivacyStateUseCase, ipScramblingStateUseCase, trackersStatisticsUseCase)
}
@@ -96,4 +98,8 @@ class DependencyContainer constructor(val app: Application) {
val internetPrivacyViewModelFactory by lazy {
InternetPrivacyViewModelFactory(ipScramblerModule, getQuickPrivacyStateUseCase, ipScramblingStateUseCase, appListUseCase)
}
+
+ val trackersViewModelFactory by lazy {
+ TrackersViewModelFactory(getQuickPrivacyStateUseCase, trackersStatisticsUseCase, appListUseCase)
+ }
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
new file mode 100644
index 0000000..d66ce76
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import foundation.e.privacycentralapp.R
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+
+class AppsAdapter(
+ private val itemsLayout: Int,
+ private val listener: (String) -> Unit
+) :
+ RecyclerView.Adapter<AppsAdapter.ViewHolder>() {
+
+ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val appName: TextView = view.findViewById(R.id.title)
+
+ fun bind(item: ApplicationDescription) {
+ appName.text = item.label
+
+ itemView.findViewById<ImageView>(R.id.icon).setImageDrawable(item.icon)
+ }
+ }
+
+ var dataSet: List<ApplicationDescription> = emptyList()
+ set(value) {
+ field = value
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(itemsLayout, parent, false)
+ val holder = ViewHolder(view)
+ holder.itemView.setOnClickListener { _ ->
+ listener(dataSet[holder.adapterPosition].packageName)
+ }
+ return holder
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val app = dataSet[position]
+ holder.bind(app)
+ }
+
+ override fun getItemCount(): Int = dataSet.size
+}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt
index 71b5e97..1817a0d 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt
@@ -22,7 +22,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageView
-import android.widget.Switch
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import foundation.e.privacycentralapp.R
@@ -60,7 +59,6 @@ class ToggleAppsAdapter(
holder.togglePermission.setOnCheckedChangeListener { _, isChecked ->
listener(dataSet[holder.adapterPosition].first.packageName, isChecked)
}
- view.findViewById<Switch>(R.id.toggle)
return holder
}
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 93fbc08..33c3f64 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
@@ -26,4 +26,20 @@ class TrackersStatisticsUseCase(
fun getPast24HoursTrackersCalls(): List<Int> {
return trackTrackersPrivacyModule.getPast24HoursTrackersCalls()
}
+
+ fun getDayMonthYearStatistics(): Triple<List<Int>, List<Int>, List<Int>> {
+ return Triple(
+ trackTrackersPrivacyModule.getPast24HoursTrackersCalls(),
+ trackTrackersPrivacyModule.getPastMonthTrackersCalls(),
+ trackTrackersPrivacyModule.getPastYearTrackersCalls()
+ )
+ }
+
+ fun getDayMonthYearCounts(): Triple<Int, Int, Int> {
+ return Triple(
+ trackTrackersPrivacyModule.getPast24HoursTrackersCount(),
+ trackTrackersPrivacyModule.getPastMonthTrackersCount(),
+ trackTrackersPrivacyModule.getPastYearTrackersCount()
+ )
+ }
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt
index 76da6a2..55ca6ec 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt
@@ -30,6 +30,10 @@ class TrackTrackersPrivacyMock : ITrackTrackersPrivacyModule {
)
}
+ override fun getPast24HoursTrackersCount(): Int {
+ return 30
+ }
+
override fun getPastMonthTrackersCalls(): List<Int> {
return listOf(
20000, 23000, 24130, 12500, 31000, 22000,
@@ -40,6 +44,10 @@ class TrackTrackersPrivacyMock : ITrackTrackersPrivacyModule {
)
}
+ override fun getPastMonthTrackersCount(): Int {
+ return 43
+ }
+
override fun getPastYearTrackersCalls(): List<Int> {
return listOf(
620000, 823000, 424130, 712500, 831000, 922000,
@@ -47,6 +55,10 @@ class TrackTrackersPrivacyMock : ITrackTrackersPrivacyModule {
)
}
+ override fun getPastYearTrackersCount(): Int {
+ return 46
+ }
+
override fun getTrackersCount(): Int {
return 72
}
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 abdf764..1b4ad39 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
@@ -18,13 +18,8 @@
package foundation.e.privacycentralapp.features.dashboard
import android.content.Intent
-import android.graphics.Color
import android.os.Bundle
-import android.text.Spannable
-import android.text.SpannableString
-import android.text.style.ForegroundColorSpan
import android.view.View
-import android.widget.TextView
import androidx.core.content.ContextCompat.getColor
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.add
@@ -143,17 +138,6 @@ class DashboardFragment :
return getString(R.string.dashboard_title)
}
- private fun addClickToMore(textView: TextView) {
- val clickToMore = SpannableString(getString(R.string.click_to_learn_more))
- clickToMore.setSpan(
- ForegroundColorSpan(Color.parseColor("#007fff")),
- 0,
- clickToMore.length,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- textView.append(clickToMore)
- }
-
override fun render(state: State) {
binding.stateLabel.text = getString(
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 9400181..0394abb 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
@@ -22,11 +22,17 @@ import foundation.e.flowmvi.Actor
import foundation.e.flowmvi.Reducer
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.TrackersStatisticsUseCase
import foundation.e.privacycentralapp.dummy.Tracker
import foundation.e.privacycentralapp.dummy.TrackersDataSource
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
// Define a state machine for Tracker feature.
class TrackersFeature(
@@ -44,16 +50,28 @@ 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 apps: List<ApplicationDescription>? = null,
+
val trackers: List<Tracker> = emptyList(),
val currentSelectedTracker: Tracker? = null
)
sealed class SingleEvent {
data class ErrorEvent(val error: String) : SingleEvent()
+ data class OpenAppDetailsEvent(val packageName: String) : SingleEvent()
object BlockerErrorEvent : SingleEvent()
}
sealed class Action {
+ object InitAction : Action()
+ data class ClickAppAction(val packageName: String) : Action()
+
object ObserveTrackers : Action()
data class SetSelectedTracker(val tracker: Tracker) : Action()
data class ToggleTrackerAction(
@@ -64,6 +82,19 @@ 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
+ ) : Effect()
+ data class AvailableAppsListEffect(
+ val apps: List<ApplicationDescription>
+ ) : Effect()
+ data class OpenAppDetailsEffect(val packageName: String) : Effect()
+ object QuickPrivacyDisabledWarningEffect : Effect()
data class TrackersLoadedEffect(val trackers: List<Tracker>) : Effect()
data class TrackerSelectedEffect(val tracker: Tracker) : Effect()
data class TrackerToggleEffect(val result: Boolean) : Effect()
@@ -74,12 +105,25 @@ class TrackersFeature(
companion object {
fun create(
initialState: State = State(),
- coroutineScope: CoroutineScope
+ coroutineScope: CoroutineScope,
+ getPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
+ trackersStatisticsUseCase: TrackersStatisticsUseCase,
+ appListUseCase: AppListUseCase
) = TrackersFeature(
initialState, coroutineScope,
reducer = { state, effect ->
when (effect) {
- is Effect.TrackersLoadedEffect -> State(effect.trackers)
+ 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)
+
+ is Effect.TrackersLoadedEffect -> State()
is Effect.TrackerSelectedEffect -> state.copy(currentSelectedTracker = effect.tracker)
is Effect.ErrorEffect -> state
is Effect.TrackerToggleEffect -> {
@@ -88,10 +132,37 @@ class TrackersFeature(
is Effect.TrackerLoadedEffect -> {
state.copy(currentSelectedTracker = effect.tracker)
}
+ else -> state
}
},
actor = { state, action ->
when (action) {
+ Action.InitAction -> merge(
+ flow {
+ val statistics = trackersStatisticsUseCase.getDayMonthYearStatistics()
+ val counts = trackersStatisticsUseCase.getDayMonthYearCounts()
+ emit(
+ Effect.TrackersStatisticsLoadedEffect(
+ dayStatistics = statistics.first,
+ dayTrackersCount = counts.first,
+ monthStatistics = statistics.second,
+ monthTrackersCount = counts.second,
+ yearStatistics = statistics.third,
+ yearTrackersCount = counts.third
+ )
+ )
+ },
+ flow {
+ val apps = appListUseCase.getAppsUsingInternet()
+ emit(Effect.AvailableAppsListEffect(apps))
+ }
+ )
+
+ is Action.ClickAppAction -> flowOf(
+ if (getPrivacyStateUseCase.isQuickPrivacyEnabled)
+ Effect.OpenAppDetailsEffect(action.packageName)
+ else Effect.QuickPrivacyDisabledWarningEffect
+ )
Action.ObserveTrackers -> TrackersDataSource.trackers.map {
Effect.TrackersLoadedEffect(
it
@@ -131,9 +202,11 @@ class TrackersFeature(
singleEventProducer = { _, _, effect ->
when (effect) {
is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message)
+ is Effect.OpenAppDetailsEffect -> SingleEvent.OpenAppDetailsEvent(effect.packageName)
is Effect.TrackerToggleEffect -> {
if (!effect.result) SingleEvent.BlockerErrorEvent else null
}
+ Effect.QuickPrivacyDisabledWarningEffect -> SingleEvent.ErrorEvent("Enabled Quick Privacy to use functionalities")
else -> null
}
}
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 e3dc941..441f39a 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
@@ -19,24 +19,39 @@ package foundation.e.privacycentralapp.features.trackers
import android.os.Bundle
import android.view.View
-import androidx.core.os.bundleOf
-import androidx.fragment.app.add
-import androidx.fragment.app.commit
+import android.widget.Toast
+import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
+import com.github.mikephil.charting.data.BarData
+import com.github.mikephil.charting.data.BarDataSet
+import com.github.mikephil.charting.data.BarEntry
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.AppsAdapter
import foundation.e.privacycentralapp.common.NavToolbarFragment
+import foundation.e.privacycentralapp.databinding.FragmentTrackersBinding
+import foundation.e.privacycentralapp.databinding.TrackersItemGraphBinding
+import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
class TrackersFragment :
NavToolbarFragment(R.layout.fragment_trackers),
MVIView<TrackersFeature.State, TrackersFeature.Action> {
- private val viewModel: TrackersViewModel by viewModels()
- private lateinit var trackersAdapter: TrackersAdapter
+ private val dependencyContainer: DependencyContainer by lazy {
+ (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer
+ }
+
+ private val viewModel: TrackersViewModel by viewModels {
+ viewModelProviderFactoryOf { dependencyContainer.trackersViewModelFactory.create() }
+ }
+
+ private lateinit var binding: FragmentTrackersBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -44,32 +59,99 @@ class TrackersFragment :
viewModel.trackersFeature.takeView(this, this@TrackersFragment)
}
lifecycleScope.launchWhenStarted {
- viewModel.submitAction(TrackersFeature.Action.ObserveTrackers)
+ viewModel.trackersFeature.singleEvents.collect { event ->
+ when (event) {
+ is TrackersFeature.SingleEvent.ErrorEvent -> {
+ displayToast(event.error)
+ }
+ is TrackersFeature.SingleEvent.OpenAppDetailsEvent -> {
+ displayToast(event.packageName)
+ }
+ }
+ }
+ }
+
+ lifecycleScope.launchWhenStarted {
+ viewModel.submitAction(TrackersFeature.Action.InitAction)
}
}
+ private fun displayToast(message: String) {
+ Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT)
+ .show()
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- trackersAdapter = TrackersAdapter {
- requireActivity().supportFragmentManager.commit {
- val bundle = bundleOf("TRACKER" to it.name)
- add<TrackerAppsFragment>(R.id.container, args = bundle)
- setReorderingAllowed(true)
- addToBackStack("trackers")
+
+ binding = FragmentTrackersBinding.bind(view)
+
+ listOf(binding.graphDay, binding.graphMonth, binding.graphYear).forEach {
+ it.graph.apply {
+ description = null
+ setTouchEnabled(false)
+ setDrawGridBackground(false)
+ setDrawBorders(false)
+ axisLeft.isEnabled = false
+ axisRight.isEnabled = false
+ xAxis.isEnabled = false
+ legend.isEnabled = false
}
- // viewModel.submitAction(TrackersFeature.Action.SetSelectedTracker(it))
}
- view.findViewById<RecyclerView>(R.id.recylcer_view_trackers)?.apply {
+
+ binding.apps.apply {
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
- adapter = trackersAdapter
+ adapter = AppsAdapter(R.layout.trackers_item_app) { packageName ->
+ viewModel.submitAction(
+ TrackersFeature.Action.ClickAppAction(packageName)
+ )
+ }
}
+
+ //
+ // requireActivity().supportFragmentManager.commit {
+ // val bundle = bundleOf("TRACKER" to it.name)
+ // add<TrackerAppsFragment>(R.id.container, args = bundle)
+ // setReorderingAllowed(true)
+ // addToBackStack("trackers")
+ // }
}
- override fun getTitle() = getString(R.string.trackers)
+ override fun getTitle() = getString(R.string.trackers_title)
override fun render(state: TrackersFeature.State) {
- trackersAdapter.setData(state.trackers)
+ 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.apps?.let {
+ binding.apps.post {
+ (binding.apps.adapter as AppsAdapter?)?.dataSet = it
+ }
+ }
+ }
+
+ private fun renderGraph(trackersCount: Int, data: List<Int>, graphBinding: TrackersItemGraphBinding) {
+ val trackersDataSet = BarDataSet(
+ data.mapIndexed { index, value -> BarEntry(index.toFloat(), value.toFloat()) },
+ getString(R.string.trackers_count_label)
+ ).apply {
+ color = ContextCompat.getColor(requireContext(), R.color.purple_chart)
+ setDrawValues(false)
+ }
+
+ graphBinding.graph.data = BarData(trackersDataSet)
+ graphBinding.graph.invalidate()
+ graphBinding.trackersCountLabel.text = getString(R.string.trackers_count_label, trackersCount)
}
override fun actions(): Flow<TrackersFeature.Action> = viewModel.actions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
index ee89887..12b66d4 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
@@ -20,17 +20,30 @@ package foundation.e.privacycentralapp.features.trackers
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import foundation.e.privacycentralapp.common.Factory
+import foundation.e.privacycentralapp.domain.usecases.AppListUseCase
+import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
+import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
-class TrackersViewModel : ViewModel() {
+class TrackersViewModel(
+ private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
+ private val trackersStatisticsUseCase: TrackersStatisticsUseCase,
+ private val appListUseCase: AppListUseCase
+) : ViewModel() {
private val _actions = MutableSharedFlow<TrackersFeature.Action>()
val actions = _actions.asSharedFlow()
val trackersFeature: TrackersFeature by lazy {
- TrackersFeature.create(coroutineScope = viewModelScope)
+ TrackersFeature.create(
+ coroutineScope = viewModelScope,
+ getPrivacyStateUseCase = getQuickPrivacyStateUseCase,
+ trackersStatisticsUseCase = trackersStatisticsUseCase,
+ appListUseCase = appListUseCase
+ )
}
fun submitAction(action: TrackersFeature.Action) {
@@ -40,3 +53,14 @@ class TrackersViewModel : ViewModel() {
}
}
}
+
+class TrackersViewModelFactory(
+ private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
+ private val trackersStatisticsUseCase: TrackersStatisticsUseCase,
+ private val appListUseCase: AppListUseCase
+) :
+ Factory<TrackersViewModel> {
+ override fun create(): TrackersViewModel {
+ return TrackersViewModel(getQuickPrivacyStateUseCase, trackersStatisticsUseCase, appListUseCase)
+ }
+}
diff --git a/app/src/main/res/layout/fragment_trackers.xml b/app/src/main/res/layout/fragment_trackers.xml
index 591b2b6..c3e7e43 100644
--- a/app/src/main/res/layout/fragment_trackers.xml
+++ b/app/src/main/res/layout/fragment_trackers.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
+<layout>
+
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@@ -20,56 +22,53 @@
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
- android:paddingLeft="32dp"
- android:paddingRight="32dp"
- tools:context=".main.MainActivity"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
>
-
<TextView
android:id="@+id/trackers_info"
- android:layout_gravity="center_horizontal"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:paddingTop="16dp"
+ android:lineSpacingExtra="5sp"
android:text="@string/manage_trackers_info"
- android:textColor="@color/black"
- android:textSize="14sp"
/>
- <TextView
- android:fontFamily="sans-serif-medium"
- android:gravity="center_vertical"
- android:id="@+id/learn_more_trackers_info"
- android:layout_height="48dp"
- android:layout_width="wrap_content"
- android:text="@string/learn_more"
- android:textColor="#007fff"
- android:textSize="14sp"
+ <include layout="@layout/trackers_item_graph"
+ android:id="@+id/graph_day"
+ android:layout_marginTop="32dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:period="@{@string/trackers_period_day}"
/>
-
- <ImageView
- android:layout_height="300dp"
- android:layout_width="wrap_content"
- android:scaleType="centerInside"
- android:src="@drawable/dummy_trackers_usage"
+ <include layout="@layout/trackers_item_graph"
+ android:id="@+id/graph_month"
+ android:layout_marginTop="16dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:period="@{@string/trackers_period_month}"
+ />
+ <include layout="@layout/trackers_item_graph"
+ android:id="@+id/graph_year"
+ android:layout_marginTop="16dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:period="@{@string/trackers_period_year}"
/>
-
<TextView
android:layout_gravity="center_horizontal"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:paddingTop="16dp"
- android:text="@string/following_trackers_in_use"
- android:textColor="@color/black"
- android:textSize="16sp"
+ android:text="@string/trackers_applist_title"
/>
<androidx.recyclerview.widget.RecyclerView
- android:id="@+id/recylcer_view_trackers"
- android:layout_height="match_parent"
+ android:id="@+id/apps"
+ android:layout_height="wrap_content"
android:layout_width="match_parent"
- tools:listitem="@layout/item_list_tracker"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
-</androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
+</layout> \ No newline at end of file
diff --git a/app/src/main/res/layout/trackers_item_app.xml b/app/src/main/res/layout/trackers_item_app.xml
new file mode 100644
index 0000000..b368664
--- /dev/null
+++ b/app/src/main/res/layout/trackers_item_app.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.appcompat.widget.LinearLayoutCompat
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_height="52dp"
+ android:layout_width="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:gravity="center_vertical"
+ android:background="?attr/selectableItemBackground"
+ >
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_height="32dp"
+ android:layout_width="32dp"
+ android:src="@drawable/ic_facebook"
+ />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_centerVertical="true"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_marginStart="16dp"
+ android:textSize="14sp"
+ tools:text="Body sensor"
+ />
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:src="@drawable/ic_chevron_right_24dp"
+ />
+</androidx.appcompat.widget.LinearLayoutCompat>
diff --git a/app/src/main/res/layout/trackers_item_graph.xml b/app/src/main/res/layout/trackers_item_graph.xml
new file mode 100644
index 0000000..afb93de
--- /dev/null
+++ b/app/src/main/res/layout/trackers_item_graph.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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/>.
+ -->
+<layout xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ >
+ <data>
+ <variable name="period" type="String" />
+ </data>
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+
+ <TextView
+ android:id="@+id/trackers_count_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16dp"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="14 trackers"
+ />
+
+ <TextView
+ android:id="@+id/graph_period_label"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textColor="@color/grey_text_2"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:layout_marginRight="16dp"
+ android:text="@{period}"
+ tools:text="24 hours"
+ />
+
+ <com.github.mikephil.charting.charts.BarChart
+ android:id="@+id/graph"
+ android:layout_height="72dp"
+ android:layout_width="match_parent"
+ app:layout_constraintTop_toBottomOf="@+id/graph_period_label"
+ />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</layout> \ 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 7433080..14ccb6f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -52,6 +52,15 @@
<string name="location_hint_latitude">Latitude</string>
<string name="location_input_error">Invalid coordinates</string>
+ <!-- Trackers -->
+ <string name="trackers_title">Manage and block</string>
+ <string name="trackers_info">See tracker usage and which trackers are present in your apps.</string>
+ <string name="trackers_count_label">%d trackers</string>
+ <string name="trackers_period_day">24 hours</string>
+ <string name="trackers_period_month">past month</string>
+ <string name="trackers_period_year">past year</string>
+ <string name="trackers_applist_title">Block trackers on each app</string>
+
<!-- -->
<string name="quick_protection_info">Quick protection enables these settings when turned on</string>
<string name="quick_protection_settings_list"> - All trackers are turned off.\n- Your geolocation will be faked.\n- Your real IP address will be hidden.</string>