From b0d9079811b08b95dd623d94c1d4338f28597d4c Mon Sep 17 00:00:00 2001 From: jacquarg Date: Sun, 31 Oct 2021 19:11:27 +0100 Subject: Add graph view on home dashboard. --- .../e/privacycentralapp/DependencyContainer.kt | 9 ++- .../domain/usecases/TrackersStatisticsUseCase.kt | 29 +++++++++ .../dummy/TrackTrackersPrivacyMock.kt | 60 ++++++++++++++++++ .../features/dashboard/DashboardFeature.kt | 74 ++++++++-------------- .../features/dashboard/DashboardFragment.kt | 73 +++++++++++++-------- .../features/dashboard/DashboardViewModel.kt | 12 ++-- .../features/dashboard/QuickProtectionFragment.kt | 40 ------------ 7 files changed, 175 insertions(+), 122 deletions(-) create mode 100644 app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt create mode 100644 app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt delete mode 100644 app/src/main/java/foundation/e/privacycentralapp/features/dashboard/QuickProtectionFragment.kt (limited to 'app/src/main/java/foundation/e/privacycentralapp') diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index 1f7cd3d..ccb0a75 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -24,6 +24,8 @@ import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.usecases.AppListUseCase import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase +import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase +import foundation.e.privacycentralapp.dummy.TrackTrackersPrivacyMock import foundation.e.privacycentralapp.features.dashboard.DashBoardViewModelFactory import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyViewModelFactory import foundation.e.privacycentralapp.features.location.FakeLocationViewModelFactory @@ -66,7 +68,7 @@ class DependencyContainer constructor(val app: Application) { // Repositories private val localStateRepository by lazy { LocalStateRepository(context) } - + private val trackTrackersPrivacyModule by lazy { TrackTrackersPrivacyMock() } // Usecases private val getQuickPrivacyStateUseCase by lazy { GetQuickPrivacyStateUseCase(localStateRepository) @@ -77,9 +79,12 @@ class DependencyContainer constructor(val app: Application) { private val appListUseCase by lazy { AppListUseCase(permissionsModule) } + private val trackersStatisticsUseCase by lazy { + TrackersStatisticsUseCase(trackTrackersPrivacyModule) + } val dashBoardViewModelFactory by lazy { - DashBoardViewModelFactory(getQuickPrivacyStateUseCase, ipScramblingStateUseCase) + DashBoardViewModelFactory(getQuickPrivacyStateUseCase, ipScramblingStateUseCase, trackersStatisticsUseCase) } val fakeLocationViewModelFactory by lazy { 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 new file mode 100644 index 0000000..93fbc08 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt @@ -0,0 +1,29 @@ +/* + * 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.domain.usecases + +import foundation.e.privacycentralapp.dummy.TrackTrackersPrivacyMock + +class TrackersStatisticsUseCase( + private val trackTrackersPrivacyModule: TrackTrackersPrivacyMock +) { + + fun getPast24HoursTrackersCalls(): List { + return trackTrackersPrivacyModule.getPast24HoursTrackersCalls() + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt new file mode 100644 index 0000000..76da6a2 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt @@ -0,0 +1,60 @@ +/* + * 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.dummy + +import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule +import foundation.e.privacymodules.trackers.Tracker + +class TrackTrackersPrivacyMock : ITrackTrackersPrivacyModule { + override fun getPast24HoursTrackersCalls(): List { + return listOf( + 2000, 2300, 130, 2500, 1000, 2000, + 2000, 2300, 130, 2500, 1000, 2000, + 2000, 2300, 130, 2500, 1000, 2000, + 2000, 2300, 130, 2500, 1000, 2000 + ) + } + + override fun getPastMonthTrackersCalls(): List { + return listOf( + 20000, 23000, 24130, 12500, 31000, 22000, + 20000, 23000, 24130, 12500, 31000, 22000, + 20000, 23000, 24130, 12500, 31000, 22000, + 20000, 23000, 24130, 12500, 31000, 22000, + 20000, 23000, 24130, 12500, 31000, 22000 + ) + } + + override fun getPastYearTrackersCalls(): List { + return listOf( + 620000, 823000, 424130, 712500, 831000, 922000, + 620000, 823000, 424130, 712500, 831000, 922000 + ) + } + + override fun getTrackersCount(): Int { + return 72 + } + + override fun getTrackersForApp(appUid: Int): List { + return listOf( + Tracker("Crashlytics", null), + Tracker(label = "Facebook", null) + ) + } +} 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 9d439ec..d38d4f6 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 @@ -26,7 +26,9 @@ import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode import foundation.e.privacycentralapp.domain.entities.LocationMode import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase +import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -45,24 +47,17 @@ class DashboardFeature( initialState, actor, reducer, coroutineScope, { message -> Log.d("DashboardFeature", message) }, singleEventProducer ) { - sealed class State() { - object LoadingState : State() - data class DisabledState( - val totalGraph: Int = 230, - // val graphData - val trackersCount: Int = 77, - val activeTrackersCount: Int = 22 - ) : State() - data class EnabledState( - val isAllTrackersBlocked: Boolean = false, - val locationMode: LocationMode = LocationMode.CUSTOM_LOCATION, - val internetPrivacyMode: InternetPrivacyMode, - val totalGraph: Int = 150, - // val graphData - val trackersCount: Int = 80, - val activeTrackersCount: Int = 10 - ) : State() - } + data class State( + val isQuickPrivacyEnabled: Boolean = false, + val isAllTrackersBlocked: Boolean = false, + val locationMode: LocationMode = LocationMode.REAL_LOCATION, + val internetPrivacyMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP, + val totalGraph: Int? = null, + // val graphData + val trackersCount: Int? = null, + val activeTrackersCount: Int? = null, + val dayStatistics: List? = null + ) sealed class SingleEvent { object NavigateToQuickProtectionSingleEvent : SingleEvent() @@ -89,6 +84,7 @@ class DashboardFeature( object NoEffect : Effect() data class UpdateStateEffect(val isEnabled: Boolean) : Effect() data class IpScramblingModeUpdatedEffect(val mode: InternetPrivacyMode) : Effect() + data class TrackersStatisticsUpdatedEffect(val dayStatistics: List) : Effect() object OpenQuickPrivacyProtectionEffect : Effect() data class OpenDashboardEffect( @@ -118,36 +114,17 @@ class DashboardFeature( fun create( coroutineScope: CoroutineScope, getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - ipScramblingStateUseCase: IpScramblingStateUseCase + ipScramblingStateUseCase: IpScramblingStateUseCase, + trackersStatisticsUseCase: TrackersStatisticsUseCase ): DashboardFeature = DashboardFeature( - initialState = State.DisabledState(), + initialState = State(), coroutineScope, reducer = { state, effect -> - if (state is State.LoadingState) state - else when (effect) { - is Effect.UpdateStateEffect -> when { - effect.isEnabled && state is State.EnabledState - || !effect.isEnabled && state is State.DisabledState -> state - effect.isEnabled && state is State.DisabledState -> State.EnabledState( - isAllTrackersBlocked = false, - locationMode = LocationMode.REAL_LOCATION, - internetPrivacyMode = InternetPrivacyMode.REAL_IP, - totalGraph = state.totalGraph, - // val graphData - trackersCount = state.trackersCount, - activeTrackersCount = state.activeTrackersCount - ) - !effect.isEnabled && state is State.EnabledState -> State.DisabledState( - totalGraph = state.totalGraph, - // val graphData - trackersCount = state.trackersCount, - activeTrackersCount = state.activeTrackersCount - ) - else -> state - } - is Effect.IpScramblingModeUpdatedEffect -> if (state is State.EnabledState) state.copy(internetPrivacyMode = effect.mode) - else state + when (effect) { + is Effect.UpdateStateEffect -> state.copy(isQuickPrivacyEnabled = effect.isEnabled) + is Effect.IpScramblingModeUpdatedEffect -> state.copy(internetPrivacyMode = effect.mode) + is Effect.TrackersStatisticsUpdatedEffect -> state.copy(dayStatistics = effect.dayStatistics) /*is Effect.OpenDashboardEffect -> State.DashboardState( effect.trackersCount, @@ -196,13 +173,11 @@ class DashboardFeature( else -> state } }, - actor = { state: State, action: Action -> + actor = { _: State, action: Action -> Log.d("Feature", "action: $action") when (action) { Action.TogglePrivacyAction -> { - if (state != State.LoadingState) { - getPrivacyStateUseCase.toggle() - } + getPrivacyStateUseCase.toggle() flowOf(Effect.NoEffect) } @@ -213,6 +188,9 @@ class DashboardFeature( }, ipScramblingStateUseCase.internetPrivacyMode.map { Effect.IpScramblingModeUpdatedEffect(it) + }, + flow { + emit(Effect.TrackersStatisticsUpdatedEffect(trackersStatisticsUseCase.getPast24HoursTrackersCalls())) } ) /* 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 fade14b..abdf764 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 @@ -30,6 +30,9 @@ import androidx.fragment.app.activityViewModels import androidx.fragment.app.add import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope +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 @@ -39,9 +42,7 @@ import foundation.e.privacycentralapp.databinding.FragmentDashboardBinding import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode import foundation.e.privacycentralapp.domain.entities.LocationMode import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf -import foundation.e.privacycentralapp.features.dashboard.DashboardFeature.State.DisabledState -import foundation.e.privacycentralapp.features.dashboard.DashboardFeature.State.EnabledState -import foundation.e.privacycentralapp.features.dashboard.DashboardFeature.State.LoadingState +import foundation.e.privacycentralapp.features.dashboard.DashboardFeature.State import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyFragment import foundation.e.privacycentralapp.features.location.FakeLocationFragment import foundation.e.privacycentralapp.features.trackers.TrackersFragment @@ -77,13 +78,6 @@ class DashboardFragment : addToBackStack("dashboard") } } - is DashboardFeature.SingleEvent.NavigateToQuickProtectionSingleEvent -> { - requireActivity().supportFragmentManager.commit { - add(R.id.container) - setReorderingAllowed(true) - addToBackStack("dashboard") - } - } is DashboardFeature.SingleEvent.NavigateToInternetActivityPrivacySingleEvent -> { requireActivity().supportFragmentManager.commit { add(R.id.container) @@ -116,6 +110,17 @@ class DashboardFragment : super.onViewCreated(view, savedInstanceState) binding = FragmentDashboardBinding.bind(view) + binding.graph.apply { + description = null + setTouchEnabled(false) + setDrawGridBackground(false) + setDrawBorders(false) + axisLeft.isEnabled = false + axisRight.isEnabled = false + xAxis.isEnabled = false + legend.isEnabled = false + } + binding.togglePrivacyCentral.setOnClickListener { viewModel.submitAction(DashboardFeature.Action.TogglePrivacyAction) } @@ -149,27 +154,26 @@ class DashboardFragment : textView.append(clickToMore) } - override fun render(state: DashboardFeature.State) { - val enabled = state is EnabledState + override fun render(state: State) { + binding.stateLabel.text = getString( - if (enabled) R.string.dashboard_state_label_on + if (state.isQuickPrivacyEnabled) R.string.dashboard_state_label_on else R.string.dashboard_state_label_off ) binding.togglePrivacyCentral.setImageResource( - if (enabled) R.drawable.ic_quick_privacy_on + if (state.isQuickPrivacyEnabled) R.drawable.ic_quick_privacy_on else R.drawable.ic_quick_privacy_off ) binding.stateLabel.setTextColor( getColor( requireContext(), - if (enabled) R.color.green_on + if (state.isQuickPrivacyEnabled) R.color.green_on else R.color.orange_off ) ) - val trackersEnabled = state is EnabledState && - state.isAllTrackersBlocked + val trackersEnabled = state.isQuickPrivacyEnabled && state.isAllTrackersBlocked binding.stateTrackers.text = getString( if (trackersEnabled) R.string.dashboard_state_trackers_on else R.string.dashboard_state_trackers_off @@ -182,7 +186,7 @@ class DashboardFragment : ) ) - val geolocEnabled = state is EnabledState && state.locationMode != LocationMode.REAL_LOCATION + val geolocEnabled = state.isQuickPrivacyEnabled && state.locationMode != LocationMode.REAL_LOCATION binding.stateGeolocation.text = getString( if (geolocEnabled) R.string.dashboard_state_geolocation_on else R.string.dashboard_state_geolocation_off @@ -195,8 +199,8 @@ class DashboardFragment : ) ) - val ipAddressEnabled = state is EnabledState && state.internetPrivacyMode != InternetPrivacyMode.REAL_IP - val isLoading = state is EnabledState && state.internetPrivacyMode in listOf( + val ipAddressEnabled = state.isQuickPrivacyEnabled && state.internetPrivacyMode != InternetPrivacyMode.REAL_IP + val isLoading = state.isQuickPrivacyEnabled && state.internetPrivacyMode in listOf( InternetPrivacyMode.HIDE_IP_LOADING, InternetPrivacyMode.REAL_IP_LOADING ) @@ -216,6 +220,23 @@ class DashboardFragment : ) ) + state.dayStatistics?.let { + val trackersDataSet = BarDataSet( + it.mapIndexed { index, value -> BarEntry(index.toFloat(), value.toFloat()) }, + getString(R.string.dashboard_graph_trackers_legend) + ).apply { + color = getColor(requireContext(), R.color.purple_chart) + setDrawValues(false) + } + + binding.graph.data = BarData(trackersDataSet) + binding.graph.invalidate() + } + + state.trackersCount?.let { + binding.graphLegend.text = getString(R.string.dashboard_graph_trackers_legend, it) + } + // binding.graphTotal.text = if (state == DashboardFeature.State.LoadingState) { // "" // } else { @@ -225,16 +246,12 @@ class DashboardFragment : // getString(R.string.dashboard_graph_total, value) // } - binding.amITracked.subtitle.text = if (state == LoadingState) "" - else { - val value = if (state is EnabledState) state.activeTrackersCount - else if (state is DisabledState) state.activeTrackersCount - else 0 // dummy - getString(R.string.dashboard_am_i_tracked_subtitle, 77, value) + state.activeTrackersCount?.let { + binding.amITracked.subtitle.text = getString(R.string.dashboard_am_i_tracked_subtitle, 77, it) } binding.myLocation.subtitle.text = getString( - if (state is EnabledState && + if (state.isQuickPrivacyEnabled && state.locationMode != LocationMode.REAL_LOCATION ) R.string.dashboard_location_subtitle_on @@ -242,7 +259,7 @@ class DashboardFragment : ) binding.internetActivityPrivacy.subtitle.text = getString( - if (state is EnabledState && + if (state.isQuickPrivacyEnabled && state.internetPrivacyMode != InternetPrivacyMode.REAL_IP ) R.string.dashboard_internet_activity_privacy_subtitle_on 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 67801eb..fa24b1d 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 @@ -22,13 +22,15 @@ import androidx.lifecycle.viewModelScope import foundation.e.privacycentralapp.common.Factory import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase +import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch class DashboardViewModel( private val getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - private val ipScramblingStateUseCase: IpScramblingStateUseCase + private val ipScramblingStateUseCase: IpScramblingStateUseCase, + private val trackersStatisticsUseCase: TrackersStatisticsUseCase ) : ViewModel() { private val _actions = MutableSharedFlow() @@ -38,7 +40,8 @@ class DashboardViewModel( DashboardFeature.create( coroutineScope = viewModelScope, getPrivacyStateUseCase = getPrivacyStateUseCase, - ipScramblingStateUseCase = ipScramblingStateUseCase + ipScramblingStateUseCase = ipScramblingStateUseCase, + trackersStatisticsUseCase = trackersStatisticsUseCase ) } @@ -51,9 +54,10 @@ class DashboardViewModel( class DashBoardViewModelFactory( private val getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - private val ipScramblingStateUseCase: IpScramblingStateUseCase + private val ipScramblingStateUseCase: IpScramblingStateUseCase, + private val trackersStatisticsUseCase: TrackersStatisticsUseCase ) : Factory { override fun create(): DashboardViewModel { - return DashboardViewModel(getPrivacyStateUseCase, ipScramblingStateUseCase) + return DashboardViewModel(getPrivacyStateUseCase, ipScramblingStateUseCase, trackersStatisticsUseCase) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/QuickProtectionFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/QuickProtectionFragment.kt deleted file mode 100644 index 981c8da..0000000 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/QuickProtectionFragment.kt +++ /dev/null @@ -1,40 +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.features.dashboard - -import android.content.Context -import androidx.activity.addCallback -import androidx.fragment.app.activityViewModels -import foundation.e.privacycentralapp.R -import foundation.e.privacycentralapp.common.NavToolbarFragment - -class QuickProtectionFragment : NavToolbarFragment(R.layout.fragment_quick_protection) { - - private val viewModel: DashboardViewModel by activityViewModels() - - override fun getTitle(): String = getString(R.string.quick_protection) - - override fun onAttach(context: Context) { - super.onAttach(context) - requireActivity().onBackPressedDispatcher.addCallback(this, true) { - // viewModel.submitAction(DashboardFeature.Action.ShowDashboardAction) - this.isEnabled = false - requireActivity().onBackPressed() - } - } -} -- cgit v1.2.1