From fa8aa18e48416f5d93875dc4e338027ab2627966 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Tue, 26 Dec 2023 11:53:16 +0100 Subject: [PATCH 1/5] 1724: Update UX on homepage. --- .../usecases/TrackersStatisticsUseCase.kt | 8 + .../features/dashboard/DashboardFragment.kt | 301 +++++-------- .../features/dashboard/DashboardState.kt | 10 +- .../features/dashboard/DashboardViewModel.kt | 131 +++--- app/src/main/res/drawable/ic_apps_24.xml | 9 + app/src/main/res/drawable/ic_block_24.xml | 9 + .../layout/dashboard_item_submenu_button.xml | 131 +++--- .../main/res/layout/fragment_dashboard.xml | 399 ++++-------------- .../main/res/layout/highlight_data_number.xml | 3 + app/src/main/res/values-night/colors.xml | 21 - app/src/main/res/values/colors.xml | 6 +- app/src/main/res/values/strings.xml | 40 +- .../trackers/data/StatsDatabase.kt | 49 +++ 13 files changed, 456 insertions(+), 661 deletions(-) create mode 100644 app/src/main/res/drawable/ic_apps_24.xml create mode 100644 app/src/main/res/drawable/ic_block_24.xml delete mode 100644 app/src/main/res/values-night/colors.xml diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt index 64520a50..5f70ae02 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt @@ -225,6 +225,14 @@ class TrackersStatisticsUseCase( ) } + suspend fun getLastMonthBlockedLeaksCount(): Int { + return statsDatabase.getBlockedLeaksCount(30, ChronoUnit.DAYS) + } + + suspend fun getLastMonthAppsWithBLockedLeaksCount(): Int { + return statsDatabase.getAppsWithBLockedLeaksCount(30, ChronoUnit.DAYS) + } + private fun getWhiteList(app: ApplicationDescription): List { return whitelistRepository.getWhiteListForApp(app).mapNotNull { trackersRepository.getTracker(it) diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt index 31c4d07c..6d70b890 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt @@ -19,26 +19,19 @@ package foundation.e.advancedprivacy.features.dashboard import android.os.Bundle -import android.text.Html -import android.text.Html.FROM_HTML_MODE_LEGACY import android.view.View import android.widget.Toast +import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getColor -import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs import foundation.e.advancedprivacy.R -import foundation.e.advancedprivacy.common.GraphHolder import foundation.e.advancedprivacy.common.NavToolbarFragment import foundation.e.advancedprivacy.databinding.FragmentDashboardBinding import foundation.e.advancedprivacy.domain.entities.FeatureState -import foundation.e.advancedprivacy.domain.entities.LocationMode -import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState import foundation.e.advancedprivacy.domain.entities.TrackerMode -import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel.Action import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel.SingleEvent import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.viewModel @@ -46,64 +39,85 @@ import org.koin.androidx.viewmodel.ext.android.viewModel class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { private val viewModel: DashboardViewModel by viewModel() - private var graphHolder: GraphHolder? = null + private lateinit var binding: FragmentDashboardBinding - private var _binding: FragmentDashboardBinding? = null - private val binding get() = _binding!! + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentDashboardBinding.bind(view) + + with(binding) { + dataBlockedTrackers.primaryMessage.apply { + setText(R.string.dashboard_data_blocked_trackers_primary) + setCompoundDrawables( + ContextCompat.getDrawable(requireContext(), R.drawable.ic_block_24), + null, null, null + ) - private var highlightIndexOnStart: Int? = null +// setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_block_24, -1, -1, -1) + } + dataBlockedTrackers.secondaryMessage + .setText(R.string.dashboard_data_blocked_trackers_secondary) + + dataApps.primaryMessage.apply { + setText(R.string.dashboard_data_apps_primary) + setCompoundDrawables( + ContextCompat.getDrawable(requireContext(), R.drawable.ic_apps_24), + null, null, null + ) +// setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_apps_24, -1, -1, -1) + } + dataApps.secondaryMessage.setText(R.string.dashboard_data_apps_secondary) + + trackersControl.title.setText(R.string.dashboard_trackers_title) + fakeLocation.title.setText(R.string.dashboard_location_title) + ipScrambling.title.setText(R.string.dashboard_ipscrambling_title) + } - private val args: DashboardFragmentArgs by navArgs() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + setOnClickListeners() - highlightIndexOnStart = args.highlightLeaks + listenViewModel() } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - _binding = FragmentDashboardBinding.bind(view) + private fun setOnClickListeners() { + with(binding) { + viewTrackersStatistics.setOnClickListener { + viewModel.onClickViewTrackersStatistics() + } - graphHolder = GraphHolder(binding.graph, requireContext()) + with(trackersControl) { + root.setOnClickListener { + viewModel.onClickTrackersControl() + } - binding.leakingAppButton.setOnClickListener { - viewModel.submitAction(Action.ShowMostLeakedApp) - } - binding.toggleTrackers.setOnClickListener { - viewModel.submitAction( - Action.ToggleTrackers( - enabled = binding.toggleTrackers.isChecked - ) - ) - } - binding.toggleLocation.setOnClickListener { - viewModel.submitAction( - Action.ToggleLocation( - enabled = binding.toggleLocation.isChecked - ) - ) - } - binding.toggleIpscrambling.setOnClickListener { - viewModel.submitAction( - Action.ToggleIpScrambling( - enabled = binding.toggleIpscrambling.isChecked - ) - ) - } - binding.myLocation.container.setOnClickListener { - viewModel.submitAction(Action.ShowFakeMyLocationAction) - } - binding.internetActivityPrivacy.container.setOnClickListener { - viewModel.submitAction(Action.ShowInternetActivityPrivacyAction) - } - binding.appsPermissions.container.setOnClickListener { - viewModel.submitAction(Action.ShowAppsPermissions) - } + switchFeature.setOnClickListener { + viewModel.onClickToggleTrackersContol(switchFeature.isChecked) + } + } + with(fakeLocation) { + + root.setOnClickListener { + viewModel.onClickFakeLocation() + } + switchFeature.setOnClickListener { + viewModel.onClickToggleFakeLocation(switchFeature.isChecked) + } + } + with(ipScrambling) { + root.setOnClickListener { + viewModel.onClickIpScrambling() + } - binding.amITracked.container.setOnClickListener { - viewModel.submitAction(Action.ShowTrackers) + switchFeature.setOnClickListener { + viewModel.onClickToggleIpScrambling(switchFeature.isChecked) + } + appsPermissions.setOnClickListener { + viewModel.onClickAppsPermissions() + } + } } + } + private fun listenViewModel() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { render(viewModel.state.value) @@ -125,6 +139,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { } } } + viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.navigate.collect(findNavController()::navigate) @@ -144,152 +159,64 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { } private fun render(state: DashboardState) { - binding.stateLabel.text = getString( - when (state.quickPrivacyState) { - QuickPrivacyState.DISABLED -> R.string.dashboard_state_title_off - QuickPrivacyState.FULL_ENABLED -> R.string.dashboard_state_title_on - QuickPrivacyState.ENABLED -> R.string.dashboard_state_title_custom - } - ) + binding.dataBlockedTrackers.number.text = state.blockedCallsCount.toString() + binding.dataApps.number.text = state.appsWithCallsCount.toString() - binding.stateIcon.setImageResource( - if (state.quickPrivacyState.isEnabled()) { - R.drawable.ic_shield_on - } else { - R.drawable.ic_shield_off - } - ) - - binding.toggleTrackers.isChecked = state.trackerMode != TrackerMode.VULNERABLE + with(binding.trackersControl) { + switchFeature.isChecked = state.trackerMode != TrackerMode.VULNERABLE - binding.stateTrackers.text = getString( - when (state.trackerMode) { - TrackerMode.DENIED -> R.string.dashboard_state_trackers_on - TrackerMode.VULNERABLE -> R.string.dashboard_state_trackers_off - TrackerMode.CUSTOM -> R.string.dashboard_state_trackers_custom - } - ) - binding.stateTrackers.setTextColor( - getColor( - requireContext(), - if (state.trackerMode == TrackerMode.VULNERABLE) { - R.color.red_off - } else { - R.color.green_valid + stateLabel.setText( + when (state.trackerMode) { + TrackerMode.DENIED -> R.string.dashboard_state_trackers_on + TrackerMode.VULNERABLE -> R.string.dashboard_state_trackers_off + TrackerMode.CUSTOM -> R.string.dashboard_state_trackers_custom } ) - ) - - binding.toggleLocation.isChecked = state.isLocationHidden - binding.stateGeolocation.text = getString( - if (state.isLocationHidden) { - R.string.dashboard_state_geolocation_on - } else { - R.string.dashboard_state_geolocation_off - } - ) - binding.stateGeolocation.setTextColor( - getColor( - requireContext(), - if (state.isLocationHidden) { - R.color.green_valid - } else { - R.color.red_off - } + stateLabel.setTextColor( + getColor( + requireContext(), + if (state.trackerMode == TrackerMode.VULNERABLE) R.color.red_off + else R.color.green_valid + ) ) - ) + } - binding.toggleIpscrambling.isChecked = state.ipScramblingMode.isChecked - val isLoading = state.ipScramblingMode.isLoading - binding.toggleIpscrambling.isEnabled = ( - state.ipScramblingMode != FeatureState.STOPPING + with(binding.fakeLocation) { + switchFeature.isChecked = state.isLocationHidden + stateLabel.setText( + if (state.isLocationHidden) R.string.dashboard_state_geolocation_on + else R.string.dashboard_state_geolocation_off ) - - binding.stateIpAddress.text = getString( - if (state.ipScramblingMode == FeatureState.ON) { - R.string.dashboard_state_ipaddress_on - } else { - R.string.dashboard_state_ipaddress_off - } - ) - - binding.stateIpAddressLoader.visibility = if (isLoading) View.VISIBLE else View.GONE - binding.stateIpAddress.visibility = if (!isLoading) View.VISIBLE else View.GONE - - binding.stateIpAddress.setTextColor( - getColor( - requireContext(), - if (state.ipScramblingMode == FeatureState.ON) { - R.color.green_valid - } else { - R.color.red_off - } + stateLabel.setTextColor( + getColor( + requireContext(), + if (state.isLocationHidden) R.color.green_valid + else R.color.red_off + ) ) - ) + } - if (state.dayStatistics?.all { it.first == 0 && it.second == 0 } == true) { - binding.graph.visibility = View.INVISIBLE - binding.graphLegend.isVisible = false - binding.leakingAppButton.isVisible = false - binding.graphEmpty.isVisible = true - } else { - binding.graph.isVisible = true - binding.graphLegend.isVisible = true - binding.leakingAppButton.isVisible = true - binding.graphEmpty.isVisible = false - state.dayStatistics?.let { graphHolder?.data = it } - state.dayLabels?.let { graphHolder?.labels = it } - state.dayGraduations?.let { graphHolder?.graduations = it } + with(binding.ipScrambling) { + switchFeature.isChecked = state.ipScramblingMode.isChecked - binding.graphLegend.text = Html.fromHtml( - getString( - R.string.dashboard_graph_trackers_legend, - state.leakedTrackersCount?.toString() ?: "No" - ), - FROM_HTML_MODE_LEGACY - ) + val isLoading = state.ipScramblingMode.isLoading + switchFeature.isEnabled = (state.ipScramblingMode != FeatureState.STOPPING) - highlightIndexOnStart?.let { - binding.graph.post { - graphHolder?.highlightIndex(it) - } - highlightIndexOnStart = null - } - } + stateLoader.visibility = if (isLoading) View.VISIBLE else View.GONE + stateLabel.visibility = if (!isLoading) View.VISIBLE else View.INVISIBLE - if (state.allowedTrackersCount != null && state.trackersCount != null) { - binding.amITracked.subTitle = getString( - R.string.dashboard_am_i_tracked_subtitle, - state.trackersCount, - state.allowedTrackersCount + stateLabel.setText( + if (state.ipScramblingMode == FeatureState.ON) R.string.dashboard_state_ipaddress_on + else R.string.dashboard_state_ipaddress_off + ) + stateLabel.setTextColor( + getColor( + requireContext(), + if (state.ipScramblingMode == FeatureState.ON) R.color.green_valid + else R.color.red_off + ) ) - } else { - binding.amITracked.subTitle = "" } - - binding.myLocation.subTitle = getString( - when (state.locationMode) { - LocationMode.REAL_LOCATION -> R.string.dashboard_location_subtitle_off - LocationMode.SPECIFIC_LOCATION -> R.string.dashboard_location_subtitle_specific - LocationMode.RANDOM_LOCATION -> R.string.dashboard_location_subtitle_random - } - ) - - binding.internetActivityPrivacy.subTitle = getString( - if (state.ipScramblingMode == FeatureState.ON) { - R.string.dashboard_internet_activity_privacy_subtitle_on - } else { - R.string.dashboard_internet_activity_privacy_subtitle_off - } - ) - - binding.executePendingBindings() - } - - override fun onDestroyView() { - super.onDestroyView() - graphHolder = null - _binding = null } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt index 774901a8..366b1d89 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt @@ -28,10 +28,8 @@ data class DashboardState( val isLocationHidden: Boolean = false, val ipScramblingMode: FeatureState = FeatureState.STOPPING, val locationMode: LocationMode = LocationMode.REAL_LOCATION, - val leakedTrackersCount: Int? = null, - val trackersCount: Int? = null, - val allowedTrackersCount: Int? = null, - val dayStatistics: List>? = null, - val dayLabels: List? = null, - val dayGraduations: List? = null + + val blockedCallsCount: Int = 0, + val appsWithCallsCount: Int = 0, + ) diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt index a36b24a3..118e4b3e 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt @@ -27,14 +27,12 @@ import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -65,9 +63,11 @@ class DashboardViewModel( getPrivacyStateUseCase.ipScramblingMode.map { _state.update { s -> s.copy(ipScramblingMode = it) } }, - trackersStatisticsUseCase.listenUpdates().flatMapLatest { + + trackersStatisticsUseCase.listenUpdates().mapLatest { fetchStatistics() }, + getPrivacyStateUseCase.trackerMode.map { _state.update { s -> s.copy(trackerMode = it) } }, @@ -88,69 +88,86 @@ class DashboardViewModel( ).collect {} } - fun submitAction(action: Action) = viewModelScope.launch { - when (action) { - is Action.ToggleTrackers -> { - getPrivacyStateUseCase.toggleTrackers(action.enabled) - // Add delay here to prevent race condition with trackers state. - delay(200) - fetchStatistics().first() - } - is Action.ToggleLocation -> getPrivacyStateUseCase.toggleLocation(action.enabled) - is Action.ToggleIpScrambling -> - getPrivacyStateUseCase.toggleIpScrambling(action.enabled) - is Action.ShowFakeMyLocationAction -> - _navigate.emit(DashboardFragmentDirections.gotoFakeLocationFragment()) - is Action.ShowAppsPermissions -> - _navigate.emit(DashboardFragmentDirections.gotoSettingsPermissionsActivity()) - is Action.ShowInternetActivityPrivacyAction -> - _navigate.emit(DashboardFragmentDirections.gotoInternetPrivacyFragment()) - is Action.ShowTrackers -> - _navigate.emit(DashboardFragmentDirections.gotoTrackersFragment()) - is Action.ShowMostLeakedApp -> actionShowMostLeakedApp() - } + fun onClickViewTrackersStatistics() = viewModelScope.launch { + _navigate.emit(DashboardFragmentDirections.gotoTrackersFragment()) } - private suspend fun fetchStatistics(): Flow = withContext(Dispatchers.IO) { - trackersStatisticsUseCase.getNonBlockedTrackersCount().map { nonBlockedTrackersCount -> - trackersStatisticsUseCase.getDayStatistics().let { (dayStatistics, trackersCount) -> - _state.update { s -> - s.copy( - dayStatistics = dayStatistics.callsBlockedNLeaked, - dayLabels = dayStatistics.periods, - dayGraduations = dayStatistics.graduations, - leakedTrackersCount = dayStatistics.trackersCount, - trackersCount = trackersCount, - allowedTrackersCount = nonBlockedTrackersCount - ) - } - } - } + fun onClickTrackersControl() = viewModelScope.launch { + _navigate.emit(DashboardFragmentDirections.gotoTrackersFragment()) + } + + fun onClickToggleTrackersContol(enabled: Boolean) = viewModelScope.launch(Dispatchers.IO) { + getPrivacyStateUseCase.toggleTrackers(enabled) + // Add delay here to prevent race condition with trackers state. + delay(200) + fetchStatistics() + } + + fun onClickFakeLocation() = viewModelScope.launch { + _navigate.emit(DashboardFragmentDirections.gotoFakeLocationFragment()) + } + + fun onClickToggleFakeLocation(enabled: Boolean) = viewModelScope.launch(Dispatchers.IO) { + getPrivacyStateUseCase.toggleLocation(enabled) } - private suspend fun actionShowMostLeakedApp() = withContext(Dispatchers.IO) { - _navigate.emit( - trackersStatisticsUseCase.getMostLeakedApp()?.let { - DashboardFragmentDirections.gotoAppTrackersFragment(appUid = it.uid) - } ?: DashboardFragmentDirections.gotoTrackersFragment() - ) + fun onClickIpScrambling() = viewModelScope.launch { + _navigate.emit(DashboardFragmentDirections.gotoInternetPrivacyFragment()) } + fun onClickToggleIpScrambling(enabled: Boolean) = viewModelScope.launch(Dispatchers.IO) { + getPrivacyStateUseCase.toggleIpScrambling(enabled) + } + + fun onClickAppsPermissions() = viewModelScope.launch { + _navigate.emit(DashboardFragmentDirections.gotoSettingsPermissionsActivity()) + } + +// fun submitAction(action: Action) = viewModelScope.launch { +// when (action) { +// is Action.ToggleTrackers -> { +// +// } +// is Action.ToggleLocation -> +// is Action.ToggleIpScrambling -> +// +// is Action.ShowFakeMyLocationAction -> +// +// is Action.ShowAppsPermissions -> +// +// is Action.ShowInternetActivityPrivacyAction -> +// +// is Action.ShowTrackers -> +// _navigate.emit(DashboardFragmentDirections.gotoTrackersFragment()) +// is Action.ShowMostLeakedApp -> actionShowMostLeakedApp() +// } +// } + + private suspend fun fetchStatistics() = withContext(Dispatchers.IO) { + val blockedCallsCount = trackersStatisticsUseCase.getLastMonthBlockedLeaksCount() + + val appsWithBlockedLeaksCount = trackersStatisticsUseCase.getLastMonthAppsWithBLockedLeaksCount() + + _state.update { + it.copy( + blockedCallsCount = blockedCallsCount, + appsWithCallsCount = appsWithBlockedLeaksCount + ) + } + } + +// private suspend fun actionShowMostLeakedApp() = withContext(Dispatchers.IO) { +// _navigate.emit( +// trackersStatisticsUseCase.getMostLeakedApp()?.let { +// DashboardFragmentDirections.gotoAppTrackersFragment(appUid = it.uid) +// } ?: DashboardFragmentDirections.gotoTrackersFragment() +// ) +// } + sealed class SingleEvent { data class ToastMessageSingleEvent( @StringRes val message: Int, val args: List = emptyList() ) : SingleEvent() } - - sealed class Action { - data class ToggleTrackers(val enabled: Boolean) : Action() - data class ToggleLocation(val enabled: Boolean) : Action() - data class ToggleIpScrambling(val enabled: Boolean) : Action() - object ShowFakeMyLocationAction : Action() - object ShowInternetActivityPrivacyAction : Action() - object ShowAppsPermissions : Action() - object ShowTrackers : Action() - object ShowMostLeakedApp : Action() - } } diff --git a/app/src/main/res/drawable/ic_apps_24.xml b/app/src/main/res/drawable/ic_apps_24.xml new file mode 100644 index 00000000..7885008d --- /dev/null +++ b/app/src/main/res/drawable/ic_apps_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_block_24.xml b/app/src/main/res/drawable/ic_block_24.xml new file mode 100644 index 00000000..161d13f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/dashboard_item_submenu_button.xml b/app/src/main/res/layout/dashboard_item_submenu_button.xml index ef03ea32..a4454c2d 100644 --- a/app/src/main/res/layout/dashboard_item_submenu_button.xml +++ b/app/src/main/res/layout/dashboard_item_submenu_button.xml @@ -1,4 +1,5 @@ - - - - - - - - + + - + android:layout_width="0dp" - + app:layout_constraintLeft_toRightOf="@+id/switch_feature" + app:layout_constraintRight_toLeftOf="@+id/chevron" - + app:layout_constraintTop_toTopOf="parent" + + app:layout_constraintBottom_toTopOf="@+id/state_label" + app:layout_constraintVertical_chainStyle="packed" + + android:maxLines="1" + + android:layout_marginStart="8dp" + android:layout_marginBottom="8dp" + + android:textColor="@color/primary_text" + android:textSize="14sp" + android:lineHeight="20dp" + android:textFontWeight="400" + android:textAllCaps="true" + tools:text="App trackers" + /> - + + + - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index b1fdaa02..fd685c92 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -18,371 +18,120 @@ android:gravity="center_horizontal" android:layout_height="match_parent" android:layout_width="match_parent" + android:paddingHorizontal="16dp" android:orientation="vertical" > - - + android:layout_height="52dp" + android:layout_marginTop="16dp" + > + - - - + - + - - - - + android:textSize="14sp" + android:lineHeight="24sp" + android:textFontWeight="500" + android:textColor="@color/primary_text" + android:layout_marginBottom="16dp" + android:text="@string/dashboard_settings_title" + /> - - - - - android:visibility="visible"/> - - + + - - - - - - - - - - - - - - + android:maxLines="1" - - - + android:textColor="@color/primary_text" + android:textSize="14sp" + android:lineHeight="20dp" + android:textFontWeight="400" + android:textAllCaps="true" - - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/highlight_data_number.xml b/app/src/main/res/layout/highlight_data_number.xml index 79381654..4bb45636 100644 --- a/app/src/main/res/layout/highlight_data_number.xml +++ b/app/src/main/res/layout/highlight_data_number.xml @@ -32,7 +32,10 @@ android:textFontWeight="400" android:textColor="@color/primary_text" android:textAllCaps="true" + android:drawablePadding="8dp" tools:text="Detected in" + tools:drawableStart="@drawable/ic_shield_alert" + /> - - - - #169659 - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 2e2b73a8..8b9d534d 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -34,12 +34,16 @@ @color/e_switch_track_on @color/e_palette_9 + @color/e_switch_track_on #32F8432E #263238 #FFFFFFFF - #28C97C + + + + #AADCFE diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3df052cb..a0e2211d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ ~ along with this program. If not, see . --> - Advanced Privacy + Advanced Privacy_4 @@ -36,34 +36,54 @@ @string/app_name - Your online privacy is protected - Your online privacy is unprotected - Custom privacy settings applied - Trackers + + Last 30 days + View Statistics + Blocked + Leaks + From + Apps + Your Privacy Settings + App trackers Vulnerable Denied Custom - Location + Geolocation Exposed - Fake - Real IP address + Protected + Real IP address Exposed Hidden + App permission request + + + + + Your online privacy is protected + Your online privacy is unprotected + Custom privacy settings applied + Trackers + + Location + + Real IP address + Personal data leakage: Today %s trackers have profiled you in the last 24 hours View Manage apps\' trackers %1$d detected trackers, %2$d allowed trackers - Manage apps\' permissions + Manage your permissions - Manage my location Real geolocation Specific fake geolocation Random fake geolocation Manage my Internet address Real IP address exposed Real IP address hidden + + Manage my Internet address Your Internet address or IP address is the identifier assigned to your phone when connected to the Internet.\n\nManage my Internet address enables you to use a fake IP address instead of your real IP address. This way, your Internet activity cannot be linked to your real IP address and to your device. diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt index 238de4e6..1b4776cb 100644 --- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt @@ -424,6 +424,55 @@ class StatsDatabase( } } + suspend fun getBlockedLeaksCount( + periodCount: Int, + periodUnit: TemporalUnit + ): Int = withContext(Dispatchers.IO) { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ?" + val selectionArg = arrayOf("" + minTimestamp) + val projection = "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME WHERE $selection", + selectionArg + ) + + var count = 0 + if (cursor.moveToNext()) { + count = cursor.getInt(0) + } + cursor.close() + db.close() + count + } + } + + suspend fun getAppsWithBLockedLeaksCount( + periodCount: Int, + periodUnit: TemporalUnit + ): Int = withContext(Dispatchers.IO) { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ? AND $COLUMN_NAME_NUMBER_BLOCKED > 0" + val selectionArg = arrayOf("" + minTimestamp) + val cursor = db.rawQuery( + "SELECT COUNT (DISTINCT $COLUMN_NAME_APPID) FROM $TABLE_NAME WHERE $selection", + selectionArg + ) + + var count = 0 + if (cursor.moveToNext()) { + count = cursor.getInt(0) + } + cursor.close() + db.close() + count + } + } + suspend fun logAccess(trackerId: String?, appId: String, blocked: Boolean) = withContext( Dispatchers.IO ) { -- GitLab From dc4ca0212faead86575f498cdc7e1d08ec49ea2b Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Tue, 26 Dec 2023 15:25:30 +0100 Subject: [PATCH 2/5] 1724: clean up outdated code and resources. --- .../e/advancedprivacy/common/GraphHolder.kt | 352 ------------------ .../usecases/TrackersStatisticsUseCase.kt | 48 --- .../features/dashboard/DashboardFragment.kt | 121 +++--- .../features/dashboard/DashboardState.kt | 6 +- .../features/dashboard/DashboardViewModel.kt | 34 -- .../trackers/graph/PeriodMarkerView.kt | 7 +- .../e/advancedprivacy/widget/WidgetUI.kt | 7 - app/src/main/res/drawable/bg_rounded.xml | 23 -- .../main/res/drawable/ic_apps_permissions.xml | 16 - app/src/main/res/drawable/ic_facebook.xml | 31 -- .../res/drawable/ic_internet_activity.xml | 28 -- app/src/main/res/drawable/ic_my_location.xml | 16 - app/src/main/res/drawable/ic_tracked.xml | 43 --- app/src/main/res/layout/chart_tooltip.xml | 62 ++- app/src/main/res/layout/chart_tooltip_2.xml | 66 ---- app/src/main/res/navigation/nav_graph.xml | 5 - app/src/main/res/values/strings.xml | 2 +- .../e/advancedprivacy/trackers/KoinModule.kt | 12 +- .../trackers/data/StatsDatabase.kt | 121 +----- .../domain/usecases/StatisticsUseCase.kt | 28 +- 20 files changed, 100 insertions(+), 928 deletions(-) delete mode 100644 app/src/main/java/foundation/e/advancedprivacy/common/GraphHolder.kt delete mode 100644 app/src/main/res/drawable/bg_rounded.xml delete mode 100644 app/src/main/res/drawable/ic_apps_permissions.xml delete mode 100644 app/src/main/res/drawable/ic_facebook.xml delete mode 100644 app/src/main/res/drawable/ic_internet_activity.xml delete mode 100644 app/src/main/res/drawable/ic_my_location.xml delete mode 100644 app/src/main/res/drawable/ic_tracked.xml delete mode 100644 app/src/main/res/layout/chart_tooltip_2.xml diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/GraphHolder.kt b/app/src/main/java/foundation/e/advancedprivacy/common/GraphHolder.kt deleted file mode 100644 index 44fb73d3..00000000 --- a/app/src/main/java/foundation/e/advancedprivacy/common/GraphHolder.kt +++ /dev/null @@ -1,352 +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.advancedprivacy.common - -import android.content.Context -import android.graphics.Canvas -import android.text.Spannable -import android.text.SpannableStringBuilder -import android.text.style.DynamicDrawableSpan -import android.text.style.ImageSpan -import android.view.View -import android.widget.TextView -import androidx.core.content.ContextCompat -import androidx.core.text.toSpannable -import androidx.core.view.isVisible -import com.github.mikephil.charting.charts.BarChart -import com.github.mikephil.charting.components.AxisBase -import com.github.mikephil.charting.components.MarkerView -import com.github.mikephil.charting.components.XAxis -import com.github.mikephil.charting.components.YAxis -import com.github.mikephil.charting.components.YAxis.AxisDependency -import com.github.mikephil.charting.data.BarData -import com.github.mikephil.charting.data.BarDataSet -import com.github.mikephil.charting.data.BarEntry -import com.github.mikephil.charting.data.Entry -import com.github.mikephil.charting.formatter.ValueFormatter -import com.github.mikephil.charting.highlight.Highlight -import com.github.mikephil.charting.listener.OnChartValueSelectedListener -import com.github.mikephil.charting.renderer.XAxisRenderer -import com.github.mikephil.charting.utils.MPPointF -import foundation.e.advancedprivacy.R -import foundation.e.advancedprivacy.common.extensions.dpToPxF -import kotlin.math.floor - -class GraphHolder(val barChart: BarChart, val context: Context, val isMarkerAbove: Boolean = true) { - var data = emptyList>() - set(value) { - field = value - refreshDataSet() - } - var labels = emptyList() - - var graduations: List? = null - - private var isHighlighted = false - - init { - barChart.description = null - barChart.setTouchEnabled(true) - barChart.setScaleEnabled(false) - - barChart.setDrawGridBackground(false) - barChart.setDrawBorders(false) - barChart.axisLeft.isEnabled = false - barChart.axisRight.isEnabled = false - - barChart.legend.isEnabled = false - - if (isMarkerAbove) prepareXAxisDashboardDay() else prepareXAxisMarkersBelow() - - val periodMarker = PeriodMarkerView(context, isMarkerAbove) - periodMarker.chartView = barChart - barChart.marker = periodMarker - - barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(e: Entry?, h: Highlight?) { - h?.let { - val index = it.x.toInt() - if (index >= 0 && - index < labels.size && - index < this@GraphHolder.data.size - ) { - val period = labels[index] - val (blocked, leaked) = this@GraphHolder.data[index] - periodMarker.setLabel(period, blocked, leaked) - } - } - isHighlighted = true - } - - override fun onNothingSelected() { - isHighlighted = false - } - }) - } - - private fun prepareXAxisDashboardDay() { - barChart.extraTopOffset = 44f - - barChart.offsetTopAndBottom(0) - - barChart.setXAxisRenderer(object : XAxisRenderer( - barChart.viewPortHandler, - barChart.xAxis, - barChart.getTransformer(AxisDependency.LEFT) - ) { - override fun renderAxisLine(c: Canvas) { - mAxisLinePaint.color = mXAxis.axisLineColor - mAxisLinePaint.strokeWidth = mXAxis.axisLineWidth - mAxisLinePaint.pathEffect = mXAxis.axisLineDashPathEffect - - // Top line - c.drawLine( - mViewPortHandler.contentLeft(), - mViewPortHandler.contentTop(), - mViewPortHandler.contentRight(), - mViewPortHandler.contentTop(), - mAxisLinePaint - ) - - // Bottom line - c.drawLine( - mViewPortHandler.contentLeft(), - mViewPortHandler.contentBottom() - 7.dpToPxF(context), - mViewPortHandler.contentRight(), - mViewPortHandler.contentBottom() - 7.dpToPxF(context), - mAxisLinePaint - ) - } - - override fun renderGridLines(c: Canvas) { - if (!mXAxis.isDrawGridLinesEnabled || !mXAxis.isEnabled) return - val clipRestoreCount = c.save() - c.clipRect(gridClippingRect) - if (mRenderGridLinesBuffer.size != mAxis.mEntryCount * 2) { - mRenderGridLinesBuffer = FloatArray(mXAxis.mEntryCount * 2) - } - val positions = mRenderGridLinesBuffer - run { - var i = 0 - while (i < positions.size) { - positions[i] = mXAxis.mEntries[i / 2] - positions[i + 1] = mXAxis.mEntries[i / 2] - i += 2 - } - } - - mTrans.pointValuesToPixel(positions) - setupGridPaint() - val gridLinePath = mRenderGridLinesPath - gridLinePath.reset() - var i = 0 - while (i < positions.size) { - val bottomY = if (graduations?.getOrNull(i / 2) != null) 0 else 3 - val x = positions[i] - gridLinePath.moveTo(x, mViewPortHandler.contentBottom() - 7.dpToPxF(context)) - gridLinePath.lineTo(x, mViewPortHandler.contentBottom() - bottomY.dpToPxF(context)) - - c.drawPath(gridLinePath, mGridPaint) - - gridLinePath.reset() - - i += 2 - } - c.restoreToCount(clipRestoreCount) - } - }) - - barChart.setDrawValueAboveBar(false) - barChart.xAxis.apply { - isEnabled = true - position = XAxis.XAxisPosition.BOTTOM - - setDrawGridLines(true) - setDrawLabels(true) - setCenterAxisLabels(false) - setLabelCount(25, true) - textColor = context.getColor(R.color.primary_text) - valueFormatter = object : ValueFormatter() { - override fun getAxisLabel(value: Float, axis: AxisBase?): String { - return graduations?.getOrNull(floor(value).toInt() + 1) ?: "" - } - } - } - } - - private fun prepareXAxisMarkersBelow() { - barChart.extraBottomOffset = 44f - - barChart.offsetTopAndBottom(0) - barChart.setDrawValueAboveBar(false) - - barChart.xAxis.apply { - isEnabled = true - position = XAxis.XAxisPosition.BOTH_SIDED - setDrawGridLines(false) - setDrawLabels(false) - } - } - - fun highlightIndex(index: Int) { - if (index >= 0 && index < data.size) { - val xPx = barChart.getTransformer(YAxis.AxisDependency.LEFT) - .getPixelForValues(index.toFloat(), 0f) - .x - val highlight = Highlight( - index.toFloat(), - 0f, - xPx.toFloat(), - 0f, - 0, - YAxis.AxisDependency.LEFT - ) - - barChart.highlightValue(highlight, true) - } - } - - private fun refreshDataSet() { - val trackersDataSet = BarDataSet( - data.mapIndexed { index, value -> - BarEntry( - index.toFloat(), - floatArrayOf(value.first.toFloat(), value.second.toFloat()) - ) - }, - "" - ).apply { - val blockedColor = ContextCompat.getColor(context, R.color.accent) - val leakedColor = ContextCompat.getColor(context, R.color.red_off) - - colors = listOf( - blockedColor, - leakedColor - ) - - setDrawValues(false) - } - - barChart.data = BarData(trackersDataSet) - barChart.invalidate() - } -} - -class PeriodMarkerView(context: Context, private val isMarkerAbove: Boolean = true) : MarkerView(context, R.layout.chart_tooltip) { - enum class ArrowPosition { LEFT, CENTER, RIGHT } - - private val arrowMargins = 10.dpToPxF(context) - private val mOffset2 = MPPointF(0f, 0f) - - private fun getArrowPosition(posX: Float): ArrowPosition { - val halfWidth = width / 2 - - return chartView?.let { chart -> - if (posX < halfWidth) { - ArrowPosition.LEFT - } else if (chart.width - posX < halfWidth) { - ArrowPosition.RIGHT - } else { - ArrowPosition.CENTER - } - } ?: ArrowPosition.CENTER - } - - private fun showArrow(position: ArrowPosition?) { - val ids = listOf( - R.id.arrow_top_left, - R.id.arrow_top_center, - R.id.arrow_top_right, - R.id.arrow_bottom_left, - R.id.arrow_bottom_center, - R.id.arrow_bottom_right - ) - - val toShow = if (isMarkerAbove) { - when (position) { - ArrowPosition.LEFT -> R.id.arrow_bottom_left - ArrowPosition.CENTER -> R.id.arrow_bottom_center - ArrowPosition.RIGHT -> R.id.arrow_bottom_right - else -> null - } - } else { - when (position) { - ArrowPosition.LEFT -> R.id.arrow_top_left - ArrowPosition.CENTER -> R.id.arrow_top_center - ArrowPosition.RIGHT -> R.id.arrow_top_right - else -> null - } - } - - ids.forEach { id -> - val showIt = id == toShow - findViewById(id)?.let { - if (it.isVisible != showIt) { - it.isVisible = showIt - } - } - } - } - - fun setLabel(period: String, blocked: Int, leaked: Int) { - val span = SpannableStringBuilder(period) - span.append(": $blocked ") - span.setSpan( - ImageSpan(context, R.drawable.ic_legend_blocked, DynamicDrawableSpan.ALIGN_BASELINE), - span.length - 1, - span.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - span.append(" $leaked ") - span.setSpan( - ImageSpan(context, R.drawable.ic_legend_leaked, DynamicDrawableSpan.ALIGN_BASELINE), - span.length - 1, - span.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - findViewById(R.id.label).text = span.toSpannable() - } - - override fun refreshContent(e: Entry?, highlight: Highlight?) { - highlight?.let { - showArrow(getArrowPosition(highlight.xPx)) - } - super.refreshContent(e, highlight) - } - - override fun getOffsetForDrawingAtPoint(posX: Float, posY: Float): MPPointF { - val x = when (getArrowPosition(posX)) { - ArrowPosition.LEFT -> -arrowMargins - ArrowPosition.RIGHT -> -width + arrowMargins - ArrowPosition.CENTER -> -width.toFloat() / 2 - } - - mOffset2.x = x - mOffset2.y = if (isMarkerAbove) { - -posY - } else { - -posY + (chartView?.height?.toFloat() ?: 0f) - height - } - - return mOffset2 - } - - override fun draw(canvas: Canvas?, posX: Float, posY: Float) { - super.draw(canvas, posX, posY) - } -} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt index 5f70ae02..5224b314 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt @@ -35,8 +35,6 @@ import java.time.temporal.ChronoUnit import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -57,38 +55,6 @@ class TrackersStatisticsUseCase( .throttleFirst(windowDuration = debounce) .onStart { emit(Unit) } - fun getDayStatistics(): Pair { - return TrackersPeriodicStatistics( - callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS), - periods = buildDayLabels(), - trackersCount = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS), - graduations = buildDayGraduations() - ) to statisticsUseCase.getContactedTrackersCount() - } - - fun getNonBlockedTrackersCount(): Flow { - return if (whitelistRepository.isBlockingEnabled) { - appListsRepository.allApps().map { apps -> - val whiteListedTrackers = mutableSetOf() - val whiteListedApps = whitelistRepository.getWhiteListedApp() - apps.forEach { app -> - if (app in whiteListedApps) { - whiteListedTrackers.addAll(statisticsUseCase.getTrackers(listOf(app))) - } else { - whiteListedTrackers.addAll(getWhiteList(app)) - } - } - whiteListedTrackers.size - } - } else { - flowOf(statisticsUseCase.getContactedTrackersCount()) - } - } - - fun getMostLeakedApp(): ApplicationDescription? { - return statisticsUseCase.getMostLeakedApp(24, ChronoUnit.HOURS) - } - fun getDayTrackersCalls() = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS) fun getDayTrackersCount() = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS) @@ -211,20 +177,6 @@ class TrackersStatisticsUseCase( ) } - suspend fun getCalls(app: ApplicationDescription): Pair { - return appListsRepository.mapReduceForHiddenApps( - app = app, - map = { - statisticsUseCase.getCalls(it, 24, ChronoUnit.HOURS) - }, - reduce = { zip -> - zip.unzip().let { (blocked, leaked) -> - blocked.sum() to leaked.sum() - } - } - ) - } - suspend fun getLastMonthBlockedLeaksCount(): Int { return statsDatabase.getBlockedLeaksCount(30, ChronoUnit.DAYS) } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt index 6d70b890..1ce0b085 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt @@ -45,76 +45,78 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { super.onViewCreated(view, savedInstanceState) binding = FragmentDashboardBinding.bind(view) - with(binding) { - dataBlockedTrackers.primaryMessage.apply { + with(binding.dataBlockedTrackers) { + primaryMessage.apply { setText(R.string.dashboard_data_blocked_trackers_primary) setCompoundDrawables( ContextCompat.getDrawable(requireContext(), R.drawable.ic_block_24), - null, null, null + null, + null, + null ) - -// setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_block_24, -1, -1, -1) } - dataBlockedTrackers.secondaryMessage + secondaryMessage .setText(R.string.dashboard_data_blocked_trackers_secondary) - - dataApps.primaryMessage.apply { + } + with(binding.dataApps) { + primaryMessage.apply { setText(R.string.dashboard_data_apps_primary) setCompoundDrawables( ContextCompat.getDrawable(requireContext(), R.drawable.ic_apps_24), - null, null, null + null, + null, + null ) -// setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_apps_24, -1, -1, -1) } - dataApps.secondaryMessage.setText(R.string.dashboard_data_apps_secondary) - - trackersControl.title.setText(R.string.dashboard_trackers_title) - fakeLocation.title.setText(R.string.dashboard_location_title) - ipScrambling.title.setText(R.string.dashboard_ipscrambling_title) + secondaryMessage.setText(R.string.dashboard_data_apps_secondary) } + binding.trackersControl.title.setText(R.string.dashboard_trackers_title) + binding.fakeLocation.title.setText(R.string.dashboard_location_title) + binding.ipScrambling.title.setText(R.string.dashboard_ipscrambling_title) + setOnClickListeners() listenViewModel() } private fun setOnClickListeners() { - with(binding) { - viewTrackersStatistics.setOnClickListener { - viewModel.onClickViewTrackersStatistics() + binding.viewTrackersStatistics.setOnClickListener { + viewModel.onClickViewTrackersStatistics() + } + + with(binding.trackersControl) { + root.setOnClickListener { + viewModel.onClickTrackersControl() } - with(trackersControl) { - root.setOnClickListener { - viewModel.onClickTrackersControl() - } + switchFeature.setOnClickListener { + viewModel.onClickToggleTrackersContol(switchFeature.isChecked) + } + } - switchFeature.setOnClickListener { - viewModel.onClickToggleTrackersContol(switchFeature.isChecked) - } + with(binding.fakeLocation) { + root.setOnClickListener { + viewModel.onClickFakeLocation() + } + switchFeature.setOnClickListener { + viewModel.onClickToggleFakeLocation(switchFeature.isChecked) } - with(fakeLocation) { + } - root.setOnClickListener { - viewModel.onClickFakeLocation() - } - switchFeature.setOnClickListener { - viewModel.onClickToggleFakeLocation(switchFeature.isChecked) - } + with(binding.ipScrambling) { + root.setOnClickListener { + viewModel.onClickIpScrambling() } - with(ipScrambling) { - root.setOnClickListener { - viewModel.onClickIpScrambling() - } - switchFeature.setOnClickListener { - viewModel.onClickToggleIpScrambling(switchFeature.isChecked) - } - appsPermissions.setOnClickListener { - viewModel.onClickAppsPermissions() - } + switchFeature.setOnClickListener { + viewModel.onClickToggleIpScrambling(switchFeature.isChecked) } } + + binding.appsPermissions.setOnClickListener { + viewModel.onClickAppsPermissions() + } } private fun listenViewModel() { @@ -176,8 +178,11 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { stateLabel.setTextColor( getColor( requireContext(), - if (state.trackerMode == TrackerMode.VULNERABLE) R.color.red_off - else R.color.green_valid + if (state.trackerMode == TrackerMode.VULNERABLE) { + R.color.red_off + } else { + R.color.green_valid + } ) ) } @@ -185,14 +190,20 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { with(binding.fakeLocation) { switchFeature.isChecked = state.isLocationHidden stateLabel.setText( - if (state.isLocationHidden) R.string.dashboard_state_geolocation_on - else R.string.dashboard_state_geolocation_off + if (state.isLocationHidden) { + R.string.dashboard_state_geolocation_on + } else { + R.string.dashboard_state_geolocation_off + } ) stateLabel.setTextColor( getColor( requireContext(), - if (state.isLocationHidden) R.color.green_valid - else R.color.red_off + if (state.isLocationHidden) { + R.color.green_valid + } else { + R.color.red_off + } ) ) } @@ -207,14 +218,20 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { stateLabel.visibility = if (!isLoading) View.VISIBLE else View.INVISIBLE stateLabel.setText( - if (state.ipScramblingMode == FeatureState.ON) R.string.dashboard_state_ipaddress_on - else R.string.dashboard_state_ipaddress_off + if (state.ipScramblingMode == FeatureState.ON) { + R.string.dashboard_state_ipaddress_on + } else { + R.string.dashboard_state_ipaddress_off + } ) stateLabel.setTextColor( getColor( requireContext(), - if (state.ipScramblingMode == FeatureState.ON) R.color.green_valid - else R.color.red_off + if (state.ipScramblingMode == FeatureState.ON) { + R.color.green_valid + } else { + R.color.red_off + } ) ) } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt index 366b1d89..e7b39b64 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt @@ -18,7 +18,6 @@ package foundation.e.advancedprivacy.features.dashboard import foundation.e.advancedprivacy.domain.entities.FeatureState -import foundation.e.advancedprivacy.domain.entities.LocationMode import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState import foundation.e.advancedprivacy.domain.entities.TrackerMode @@ -27,9 +26,6 @@ data class DashboardState( val trackerMode: TrackerMode = TrackerMode.VULNERABLE, val isLocationHidden: Boolean = false, val ipScramblingMode: FeatureState = FeatureState.STOPPING, - val locationMode: LocationMode = LocationMode.REAL_LOCATION, - val blockedCallsCount: Int = 0, - val appsWithCallsCount: Int = 0, - + val appsWithCallsCount: Int = 0 ) diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt index 118e4b3e..6649606a 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt @@ -57,9 +57,6 @@ class DashboardViewModel( suspend fun doOnStartedState() = withContext(Dispatchers.IO) { merge( - getPrivacyStateUseCase.quickPrivacyState.map { - _state.update { s -> s.copy(quickPrivacyState = it) } - }, getPrivacyStateUseCase.ipScramblingMode.map { _state.update { s -> s.copy(ipScramblingMode = it) } }, @@ -74,9 +71,6 @@ class DashboardViewModel( getPrivacyStateUseCase.isLocationHidden.map { _state.update { s -> s.copy(isLocationHidden = it) } }, - getPrivacyStateUseCase.locationMode.map { - _state.update { s -> s.copy(locationMode = it) } - }, getPrivacyStateUseCase.otherVpnRunning.map { _singleEvents.emit( SingleEvent.ToastMessageSingleEvent( @@ -123,26 +117,6 @@ class DashboardViewModel( _navigate.emit(DashboardFragmentDirections.gotoSettingsPermissionsActivity()) } -// fun submitAction(action: Action) = viewModelScope.launch { -// when (action) { -// is Action.ToggleTrackers -> { -// -// } -// is Action.ToggleLocation -> -// is Action.ToggleIpScrambling -> -// -// is Action.ShowFakeMyLocationAction -> -// -// is Action.ShowAppsPermissions -> -// -// is Action.ShowInternetActivityPrivacyAction -> -// -// is Action.ShowTrackers -> -// _navigate.emit(DashboardFragmentDirections.gotoTrackersFragment()) -// is Action.ShowMostLeakedApp -> actionShowMostLeakedApp() -// } -// } - private suspend fun fetchStatistics() = withContext(Dispatchers.IO) { val blockedCallsCount = trackersStatisticsUseCase.getLastMonthBlockedLeaksCount() @@ -156,14 +130,6 @@ class DashboardViewModel( } } -// private suspend fun actionShowMostLeakedApp() = withContext(Dispatchers.IO) { -// _navigate.emit( -// trackersStatisticsUseCase.getMostLeakedApp()?.let { -// DashboardFragmentDirections.gotoAppTrackersFragment(appUid = it.uid) -// } ?: DashboardFragmentDirections.gotoTrackersFragment() -// ) -// } - sealed class SingleEvent { data class ToastMessageSingleEvent( @StringRes val message: Int, diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/graph/PeriodMarkerView.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/graph/PeriodMarkerView.kt index be7e81bc..eb651bee 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/graph/PeriodMarkerView.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/graph/PeriodMarkerView.kt @@ -34,7 +34,7 @@ import com.github.mikephil.charting.utils.MPPointF import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.common.extensions.dpToPxF -class PeriodMarkerView(context: Context) : MarkerView(context, R.layout.chart_tooltip_2) { +class PeriodMarkerView(context: Context) : MarkerView(context, R.layout.chart_tooltip) { enum class ArrowPosition { LEFT, CENTER, RIGHT } private val arrowMargins = 10.dpToPxF(context) @@ -56,9 +56,6 @@ class PeriodMarkerView(context: Context) : MarkerView(context, R.layout.chart_to private fun showArrow(position: ArrowPosition?) { val ids = listOf( - R.id.arrow_top_left, - R.id.arrow_top_center, - R.id.arrow_top_right, R.id.arrow_bottom_left, R.id.arrow_bottom_center, R.id.arrow_bottom_right @@ -85,7 +82,7 @@ class PeriodMarkerView(context: Context) : MarkerView(context, R.layout.chart_to val span = SpannableStringBuilder(period) span.append(" | ") span.setSpan( - ImageSpan(context, R.drawable.ic_legend_blocked_2, DynamicDrawableSpan.ALIGN_BASELINE), + ImageSpan(context, R.drawable.ic_legend_blocked, DynamicDrawableSpan.ALIGN_BASELINE), span.length - 1, span.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE diff --git a/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt b/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt index 5d5a1639..3d58999b 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt @@ -34,7 +34,6 @@ import foundation.e.advancedprivacy.common.extensions.dpToPxF import foundation.e.advancedprivacy.domain.entities.FeatureState import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState import foundation.e.advancedprivacy.domain.entities.TrackerMode -import foundation.e.advancedprivacy.features.dashboard.DashboardFragmentArgs import foundation.e.advancedprivacy.main.MainActivity import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_IPSCRAMBLING import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_LOCATION @@ -211,12 +210,6 @@ fun render(context: Context, state: State, appWidgetManager: AppWidgetManager) { // leaked (the bar above) val topPadding = graphHeightPx - (blocked + leaked) * ratio setViewPadding(leakedBarIds[index], 0, topPadding.toInt(), 0, 0) - - val highlightPIntent = MainActivity.deepLinkBuilder(context) - .setDestination(R.id.dashboardFragment) - .setArguments(DashboardFragmentArgs(highlightLeaks = index).toBundle()) - .createPendingIntent() - setOnClickPendingIntent(containerBarIds[index], highlightPIntent) } setTextViewText( diff --git a/app/src/main/res/drawable/bg_rounded.xml b/app/src/main/res/drawable/bg_rounded.xml deleted file mode 100644 index 06771656..00000000 --- a/app/src/main/res/drawable/bg_rounded.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/drawable/ic_apps_permissions.xml b/app/src/main/res/drawable/ic_apps_permissions.xml deleted file mode 100644 index 5e7a5706..00000000 --- a/app/src/main/res/drawable/ic_apps_permissions.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_facebook.xml b/app/src/main/res/drawable/ic_facebook.xml deleted file mode 100644 index 49597b37..00000000 --- a/app/src/main/res/drawable/ic_facebook.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/ic_internet_activity.xml b/app/src/main/res/drawable/ic_internet_activity.xml deleted file mode 100644 index 83695ad6..00000000 --- a/app/src/main/res/drawable/ic_internet_activity.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_my_location.xml b/app/src/main/res/drawable/ic_my_location.xml deleted file mode 100644 index 5d70d16e..00000000 --- a/app/src/main/res/drawable/ic_my_location.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_tracked.xml b/app/src/main/res/drawable/ic_tracked.xml deleted file mode 100644 index 6cef5372..00000000 --- a/app/src/main/res/drawable/ic_tracked.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/chart_tooltip.xml b/app/src/main/res/layout/chart_tooltip.xml index a88bc6fe..6fabd50d 100644 --- a/app/src/main/res/layout/chart_tooltip.xml +++ b/app/src/main/res/layout/chart_tooltip.xml @@ -1,4 +1,5 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 1047da66..04564373 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -46,11 +46,6 @@ android:id="@+id/goto_settingsPermissionsActivity" app:destination="@id/settingsPermissionsActivity" /> - . --> - Advanced Privacy_4 + Advanced Privacy diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt index 34b4e7a8..6a5bcef6 100644 --- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt @@ -37,18 +37,10 @@ val trackersModule = module { singleOf(::TrackersRepository) single { - StatsDatabase( - context = androidContext(), - trackersRepository = get() - ) + StatsDatabase(context = androidContext()) } - single { - StatisticsUseCase( - database = get(), - appListsRepository = get() - ) - } + singleOf(::StatisticsUseCase) single { WhitelistRepository( diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt index 1b4776cb..da6ee868 100644 --- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt @@ -31,7 +31,6 @@ import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry. import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TIMESTAMP import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TRACKER import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.TABLE_NAME -import foundation.e.advancedprivacy.trackers.domain.entities.Tracker import java.time.Instant import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @@ -45,8 +44,7 @@ import kotlinx.coroutines.withContext import timber.log.Timber class StatsDatabase( - context: Context, - private val trackersRepository: TrackersRepository + context: Context ) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { @@ -247,75 +245,6 @@ class StatsDatabase( } } - fun getContactedTrackersCount(): Int { - synchronized(lock) { - val db = readableDatabase - var query = "SELECT DISTINCT $COLUMN_NAME_TRACKER FROM $TABLE_NAME" - - val cursor = db.rawQuery(query, arrayOf()) - var count = 0 - while (cursor.moveToNext()) { - trackersRepository.getTracker(cursor.getString(COLUMN_NAME_TRACKER))?.let { - count++ - } - } - cursor.close() - db.close() - return count - } - } - - fun getCalls(appId: String, periodCount: Int, periodUnit: TemporalUnit): Pair { - synchronized(lock) { - val minTimestamp = getPeriodStartTs(periodCount, periodUnit) - val db = readableDatabase - val selection = "$COLUMN_NAME_APPID = ? AND " + - "$COLUMN_NAME_TIMESTAMP >= ?" - val selectionArg = arrayOf("" + appId, "" + minTimestamp) - val projection = - "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," + - "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" - val cursor = db.rawQuery( - "SELECT $projection FROM $TABLE_NAME WHERE $selection", - selectionArg - ) - var calls: Pair = 0 to 0 - if (cursor.moveToNext()) { - val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) - val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) - calls = blocked to contacted - blocked - } - cursor.close() - db.close() - return calls - } - } - - fun getMostLeakedAppId(periodCount: Int, periodUnit: TemporalUnit): String { - synchronized(lock) { - val minTimestamp = getPeriodStartTs(periodCount, periodUnit) - val db = readableDatabase - val selection = "$COLUMN_NAME_TIMESTAMP >= ?" - val selectionArg = arrayOf("" + minTimestamp) - val projection = "$COLUMN_NAME_APPID, " + - "SUM($COLUMN_NAME_NUMBER_CONTACTED - $COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_LEAKED_SUM" - val cursor = db.rawQuery( - "SELECT $projection FROM $TABLE_NAME" + - " WHERE $selection" + - " GROUP BY $COLUMN_NAME_APPID" + - " ORDER BY $PROJECTION_NAME_LEAKED_SUM DESC LIMIT 1", - selectionArg - ) - var appId = "" - if (cursor.moveToNext()) { - appId = cursor.getString(COLUMN_NAME_APPID) - } - cursor.close() - db.close() - return appId - } - } - fun getDistinctTrackerAndApp(periodStart: Instant): List> { synchronized(lock) { val db = readableDatabase @@ -332,7 +261,6 @@ class StatsDatabase( null, null, null - ) val res = mutableListOf>() @@ -424,10 +352,7 @@ class StatsDatabase( } } - suspend fun getBlockedLeaksCount( - periodCount: Int, - periodUnit: TemporalUnit - ): Int = withContext(Dispatchers.IO) { + suspend fun getBlockedLeaksCount(periodCount: Int, periodUnit: TemporalUnit): Int = withContext(Dispatchers.IO) { synchronized(lock) { val minTimestamp = getPeriodStartTs(periodCount, periodUnit) val db = readableDatabase @@ -449,10 +374,7 @@ class StatsDatabase( } } - suspend fun getAppsWithBLockedLeaksCount( - periodCount: Int, - periodUnit: TemporalUnit - ): Int = withContext(Dispatchers.IO) { + suspend fun getAppsWithBLockedLeaksCount(periodCount: Int, periodUnit: TemporalUnit): Int = withContext(Dispatchers.IO) { synchronized(lock) { val minTimestamp = getPeriodStartTs(periodCount, periodUnit) val db = readableDatabase @@ -577,43 +499,6 @@ class StatsDatabase( } } - suspend fun getTrackers(appIds: List?): List = withContext(Dispatchers.IO) { - synchronized(lock) { - val columns = arrayOf(COLUMN_NAME_TRACKER, COLUMN_NAME_APPID) - var selection: String? = null - - var selectionArg: Array? = null - appIds?.let { appIds -> - selection = "$COLUMN_NAME_APPID IN (${appIds.joinToString(", ") { "'$it'" }})" - selectionArg = arrayOf() - } - - val db = readableDatabase - val cursor = db.query( - true, - TABLE_NAME, - columns, - selection, - selectionArg, - null, - null, - null, - null - ) - val trackers: MutableList = ArrayList() - while (cursor.moveToNext()) { - val trackerId = cursor.getString(COLUMN_NAME_TRACKER) - val tracker = trackersRepository.getTracker(trackerId) - if (tracker != null) { - trackers.add(tracker) - } - } - cursor.close() - db.close() - trackers - } - } - class StatEntry { var appId = "" var sum_contacted = 0 diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt index d629b801..a36a6bf1 100644 --- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt @@ -18,15 +18,11 @@ package foundation.e.advancedprivacy.trackers.domain.usecases -import foundation.e.advancedprivacy.data.repositories.AppListsRepository -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription import foundation.e.advancedprivacy.trackers.data.StatsDatabase -import foundation.e.advancedprivacy.trackers.domain.entities.Tracker import java.time.temporal.TemporalUnit class StatisticsUseCase( - private val database: StatsDatabase, - private val appListsRepository: AppListsRepository + private val database: StatsDatabase ) { fun getTrackersCallsOnPeriod(periodsCount: Int, periodUnit: TemporalUnit): List> { return database.getTrackersCallsOnPeriod(periodsCount, periodUnit) @@ -35,26 +31,4 @@ class StatisticsUseCase( fun getActiveTrackersByPeriod(periodsCount: Int, periodUnit: TemporalUnit): Int { return database.getActiveTrackersByPeriod(periodsCount, periodUnit) } - - fun getContactedTrackersCount(): Int { - return database.getContactedTrackersCount() - } - - suspend fun getTrackers(apps: List?): List { - return database.getTrackers(apps?.map { it.apId }) - } - - fun getCalls(app: ApplicationDescription, periodCount: Int, periodUnit: TemporalUnit): Pair { - return database.getCalls(app.apId, periodCount, periodUnit) - } - - fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): ApplicationDescription? { - return appListsRepository.getApp(database.getMostLeakedAppId(periodCount, periodUnit)) - } - - private fun Map.mapByAppIdToApp(): Map { - return entries.mapNotNull { (apId, value) -> - appListsRepository.getApp(apId)?.let { it to value } - }.toMap() - } } -- GitLab From 2c3349abef1764fce9b1fb453b23b1dd34e529e4 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Fri, 5 Jan 2024 09:48:53 +0100 Subject: [PATCH 3/5] 1724: review fixes. --- .../usecases/TrackersStatisticsUseCase.kt | 4 +- .../features/dashboard/DashboardFragment.kt | 53 +++++++------------ app/src/main/res/drawable/ic_apps_24.xml | 17 ++++++ app/src/main/res/drawable/ic_block_24.xml | 17 ++++++ app/src/main/res/values/strings.xml | 3 -- 5 files changed, 56 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt index 5224b314..374afe66 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt @@ -178,11 +178,11 @@ class TrackersStatisticsUseCase( } suspend fun getLastMonthBlockedLeaksCount(): Int { - return statsDatabase.getBlockedLeaksCount(30, ChronoUnit.DAYS) + return statsDatabase.getBlockedLeaksCount(Period.MONTH.periodsCount, Period.MONTH.periodUnit) } suspend fun getLastMonthAppsWithBLockedLeaksCount(): Int { - return statsDatabase.getAppsWithBLockedLeaksCount(30, ChronoUnit.DAYS) + return statsDatabase.getAppsWithBLockedLeaksCount(Period.MONTH.periodsCount, Period.MONTH.periodUnit) } private fun getWhiteList(app: ApplicationDescription): List { diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt index 1ce0b085..5cf7ce8f 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt @@ -23,6 +23,7 @@ import android.view.View import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getColor +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -55,8 +56,8 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { null ) } - secondaryMessage - .setText(R.string.dashboard_data_blocked_trackers_secondary) + + secondaryMessage.setText(R.string.dashboard_data_blocked_trackers_secondary) } with(binding.dataApps) { primaryMessage.apply { @@ -68,6 +69,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { null ) } + secondaryMessage.setText(R.string.dashboard_data_apps_secondary) } @@ -99,6 +101,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { root.setOnClickListener { viewModel.onClickFakeLocation() } + switchFeature.setOnClickListener { viewModel.onClickToggleFakeLocation(switchFeature.isChecked) } @@ -175,16 +178,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { } ) - stateLabel.setTextColor( - getColor( - requireContext(), - if (state.trackerMode == TrackerMode.VULNERABLE) { - R.color.red_off - } else { - R.color.green_valid - } - ) - ) + stateLabel.setTextColor(getStateColor(state.trackerMode != TrackerMode.VULNERABLE)) } with(binding.fakeLocation) { @@ -196,16 +190,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { R.string.dashboard_state_geolocation_off } ) - stateLabel.setTextColor( - getColor( - requireContext(), - if (state.isLocationHidden) { - R.color.green_valid - } else { - R.color.red_off - } - ) - ) + stateLabel.setTextColor(getStateColor(state.isLocationHidden)) } with(binding.ipScrambling) { @@ -214,7 +199,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { val isLoading = state.ipScramblingMode.isLoading switchFeature.isEnabled = (state.ipScramblingMode != FeatureState.STOPPING) - stateLoader.visibility = if (isLoading) View.VISIBLE else View.GONE + stateLoader.isVisible = isLoading stateLabel.visibility = if (!isLoading) View.VISIBLE else View.INVISIBLE stateLabel.setText( @@ -224,16 +209,18 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { R.string.dashboard_state_ipaddress_off } ) - stateLabel.setTextColor( - getColor( - requireContext(), - if (state.ipScramblingMode == FeatureState.ON) { - R.color.green_valid - } else { - R.color.red_off - } - ) - ) + stateLabel.setTextColor(getStateColor(state.ipScramblingMode == FeatureState.ON)) } } + + private fun getStateColor(isActive: Boolean): Int { + return getColor( + requireContext(), + if (isActive) { + R.color.green_valid + } else { + R.color.red_off + } + ) + } } diff --git a/app/src/main/res/drawable/ic_apps_24.xml b/app/src/main/res/drawable/ic_apps_24.xml index 7885008d..7e205367 100644 --- a/app/src/main/res/drawable/ic_apps_24.xml +++ b/app/src/main/res/drawable/ic_apps_24.xml @@ -1,3 +1,20 @@ + + + Hidden App permission request - - - Your online privacy is protected Your online privacy is unprotected Custom privacy settings applied -- GitLab From 1337f65394ca843f4b571597231b9639550bd02e Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Tue, 9 Jan 2024 19:19:18 +0100 Subject: [PATCH 4/5] 1724: review fixes, use setOnCheckedChangeListener --- .../features/dashboard/DashboardFragment.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt index 5cf7ce8f..9cdffa45 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt @@ -92,8 +92,8 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { viewModel.onClickTrackersControl() } - switchFeature.setOnClickListener { - viewModel.onClickToggleTrackersContol(switchFeature.isChecked) + switchFeature.setOnCheckedChangeListener { _, isChecked -> + viewModel.onClickToggleTrackersContol(isChecked) } } @@ -102,8 +102,8 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { viewModel.onClickFakeLocation() } - switchFeature.setOnClickListener { - viewModel.onClickToggleFakeLocation(switchFeature.isChecked) + switchFeature.setOnCheckedChangeListener { _, isChecked -> + viewModel.onClickToggleFakeLocation(isChecked) } } @@ -112,8 +112,8 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { viewModel.onClickIpScrambling() } - switchFeature.setOnClickListener { - viewModel.onClickToggleIpScrambling(switchFeature.isChecked) + switchFeature.setOnCheckedChangeListener { _, isChecked -> + viewModel.onClickToggleIpScrambling(isChecked) } } -- GitLab From f63e25d365e52cb1a99bd01ca906f449097fca6d Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Thu, 11 Jan 2024 17:35:48 +0100 Subject: [PATCH 5/5] 1724: reviews fix, remove deprecated update data call. --- .../advancedprivacy/features/dashboard/DashboardViewModel.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt index 6649606a..a820be65 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt @@ -26,7 +26,6 @@ import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow @@ -92,9 +91,6 @@ class DashboardViewModel( fun onClickToggleTrackersContol(enabled: Boolean) = viewModelScope.launch(Dispatchers.IO) { getPrivacyStateUseCase.toggleTrackers(enabled) - // Add delay here to prevent race condition with trackers state. - delay(200) - fetchStatistics() } fun onClickFakeLocation() = viewModelScope.launch { -- GitLab