diff options
| author | jacquarg <guillaume.jacquart@hoodbrains.com> | 2021-11-01 21:24:09 +0100 |
|---|---|---|
| committer | jacquarg <guillaume.jacquart@hoodbrains.com> | 2021-11-01 21:24:09 +0100 |
| commit | a484bf584f4163c8a0a1260e81d598fdec87ff3b (patch) | |
| tree | d6895488aafed08ef1c178a3b7713024edc02635 /app/src/main/java/foundation/e/privacycentralapp/features | |
| parent | b0d9079811b08b95dd623d94c1d4338f28597d4c (diff) | |
| download | advanced-privacy-a484bf584f4163c8a0a1260e81d598fdec87ff3b.tar.gz | |
Add trackers UI
Diffstat (limited to 'app/src/main/java/foundation/e/privacycentralapp/features')
4 files changed, 201 insertions, 38 deletions
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) + } +} |
