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 | |
parent | b0d9079811b08b95dd623d94c1d4338f28597d4c (diff) |
Add trackers UI
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> |