From 3ca4c651aff84c551101337984bf1d457d892a1a Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Mon, 25 Apr 2022 15:16:21 +0000 Subject: 5311 main toggle wording, 5321 default settings --- .../e/privacycentralapp/DependencyContainer.kt | 5 +- .../data/repositories/LocalStateRepository.kt | 38 ++++++++++----- .../domain/entities/QuickPrivacyState.kt | 24 ++++++++++ .../domain/usecases/FakeLocationStateUseCase.kt | 13 +++-- .../domain/usecases/GetQuickPrivacyStateUseCase.kt | 55 ++++++++++++++++++++-- .../domain/usecases/IpScramblingStateUseCase.kt | 8 +++- .../domain/usecases/TrackersStateUseCase.kt | 12 +---- .../features/dashboard/DashboardFeature.kt | 55 +++++++++++++--------- .../features/dashboard/DashboardFragment.kt | 43 +++++++++-------- .../features/dashboard/DashboardViewModel.kt | 14 +----- .../e/privacycentralapp/widget/Widget.kt | 30 ++++-------- .../widget/WidgetCommandReceiver.kt | 2 +- .../e/privacycentralapp/widget/WidgetUI.kt | 38 +++++++-------- app/src/main/res/drawable-night/ic_shield_off.xml | 19 ++++++++ app/src/main/res/drawable-night/ic_shield_on.xml | 18 +++++++ app/src/main/res/drawable/ic_shield_off.xml | 25 +++++----- app/src/main/res/drawable/ic_shield_off_white.xml | 19 ++++++++ app/src/main/res/drawable/ic_shield_on.xml | 25 +++++----- app/src/main/res/drawable/ic_shield_on_white.xml | 18 +++++++ app/src/main/res/values/strings.xml | 3 ++ 20 files changed, 302 insertions(+), 162 deletions(-) create mode 100644 app/src/main/java/foundation/e/privacycentralapp/domain/entities/QuickPrivacyState.kt create mode 100644 app/src/main/res/drawable-night/ic_shield_off.xml create mode 100644 app/src/main/res/drawable-night/ic_shield_on.xml create mode 100644 app/src/main/res/drawable/ic_shield_off_white.xml create mode 100644 app/src/main/res/drawable/ic_shield_on_white.xml diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index fa4a3e3..4a790c6 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -104,7 +104,7 @@ class DependencyContainer(val app: Application) { // ViewModelFactories val dashBoardViewModelFactory by lazy { - DashBoardViewModelFactory(getQuickPrivacyStateUseCase, ipScramblingStateUseCase, trackersStatisticsUseCase, trackersStateUseCase, fakeLocationStateUseCase) + DashBoardViewModelFactory(getQuickPrivacyStateUseCase, trackersStatisticsUseCase) } val fakeLocationViewModelFactory by lazy { @@ -138,10 +138,7 @@ class DependencyContainer(val app: Application) { Widget.startListening( context, getQuickPrivacyStateUseCase, - ipScramblingStateUseCase, trackersStatisticsUseCase, - trackersStateUseCase, - fakeLocationStateUseCase ) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt index 9a7fd15..136b20f 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt @@ -18,6 +18,8 @@ package foundation.e.privacycentralapp.data.repositories import android.content.Context +import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode +import foundation.e.privacycentralapp.domain.entities.LocationMode import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -26,6 +28,7 @@ class LocalStateRepository(context: Context) { private const val SHARED_PREFS_FILE = "localState" private const val KEY_QUICK_PRIVACY = "quickPrivacy" private const val KEY_IP_SCRAMBLING = "ipScrambling" + private const val KEY_FAKE_LOCATION = "fakeLocation" private const val KEY_FAKE_LATITUDE = "fakeLatitude" private const val KEY_FAKE_LONGITUDE = "fakeLongitude" private const val KEY_FIRST_BOOT = "firstBoot" @@ -35,43 +38,52 @@ class LocalStateRepository(context: Context) { private val quickPrivacyEnabledMutableFlow = MutableStateFlow(sharedPref.getBoolean(KEY_QUICK_PRIVACY, false)) - var isQuickPrivacyEnabled: Boolean - get() = quickPrivacyEnabledMutableFlow.value - set(value) { - set(KEY_QUICK_PRIVACY, value) - quickPrivacyEnabledMutableFlow.value = value - } + val isQuickPrivacyEnabled: Boolean get() = quickPrivacyEnabledMutableFlow.value + + fun setQuickPrivacyReturnIsFirstActivation(value: Boolean): Boolean { + val isFirstActivation = value && !sharedPref.contains(KEY_QUICK_PRIVACY) + set(KEY_QUICK_PRIVACY, value) + quickPrivacyEnabledMutableFlow.value = value + return isFirstActivation + } var quickPrivacyEnabledFlow: Flow = quickPrivacyEnabledMutableFlow + val areAllTrackersBlocked: MutableStateFlow = MutableStateFlow(false) + var fakeLocation: Pair? - get() = if (sharedPref.contains(KEY_FAKE_LATITUDE) && sharedPref.contains( - KEY_FAKE_LONGITUDE - ) - ) + get() = if (sharedPref.getBoolean(KEY_FAKE_LOCATION, true)) Pair( - sharedPref.getFloat(KEY_FAKE_LATITUDE, 0f), - sharedPref.getFloat(KEY_FAKE_LONGITUDE, 0f) + // Initial default value is Quezon City + sharedPref.getFloat(KEY_FAKE_LATITUDE, 14.6760f), + sharedPref.getFloat(KEY_FAKE_LONGITUDE, 121.0437f) ) else null + set(value) { if (value == null) { sharedPref.edit() + .putBoolean(KEY_FAKE_LOCATION, false) .remove(KEY_FAKE_LATITUDE) .remove(KEY_FAKE_LONGITUDE) .commit() } else { sharedPref.edit() + .putBoolean(KEY_FAKE_LOCATION, true) .putFloat(KEY_FAKE_LATITUDE, value.first) .putFloat(KEY_FAKE_LONGITUDE, value.second) .commit() } } + val locationMode: MutableStateFlow = MutableStateFlow(LocationMode.REAL_LOCATION) + var isIpScramblingEnabled: Boolean - get() = sharedPref.getBoolean(KEY_IP_SCRAMBLING, false) + get() = sharedPref.getBoolean(KEY_IP_SCRAMBLING, true) set(value) = set(KEY_IP_SCRAMBLING, value) + val internetPrivacyMode: MutableStateFlow = MutableStateFlow(InternetPrivacyMode.REAL_IP) + var firstBoot: Boolean get() = sharedPref.getBoolean(KEY_FIRST_BOOT, true) set(value) = set(KEY_FIRST_BOOT, value) diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/QuickPrivacyState.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/QuickPrivacyState.kt new file mode 100644 index 0000000..3257402 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/QuickPrivacyState.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacycentralapp.domain.entities + +enum class QuickPrivacyState { + DISABLED, ENABLED, FULL_ENABLED; + + fun isEnabled(): Boolean = this != DISABLED +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt index 32affe0..c07657a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt @@ -33,7 +33,6 @@ import foundation.e.privacymodules.permissions.data.AppOpModes import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlin.random.Random @@ -47,8 +46,8 @@ class FakeLocationStateUseCase( private val appContext: Context, private val coroutineScope: CoroutineScope ) { - private val _locationMode = MutableStateFlow(LocationMode.REAL_LOCATION) - val locationMode: StateFlow = _locationMode + // private val _locationMode = MutableStateFlow(LocationMode.REAL_LOCATION) + // val locationMode: StateFlow = _locationMode init { coroutineScope.launch { @@ -63,14 +62,14 @@ class FakeLocationStateUseCase( fun getLocationMode(): Triple { val fakeLocation = localStateRepository.fakeLocation - return if (fakeLocation != null && _locationMode.value == LocationMode.SPECIFIC_LOCATION) { + return if (fakeLocation != null && localStateRepository.locationMode.value == LocationMode.SPECIFIC_LOCATION) { Triple( LocationMode.SPECIFIC_LOCATION, fakeLocation.first, fakeLocation.second ) } else { - Triple(_locationMode.value, null, null) + Triple(localStateRepository.locationMode.value, null, null) } } @@ -97,11 +96,11 @@ class FakeLocationStateUseCase( } fakeLocationModule.startFakeLocation() fakeLocationModule.setFakeLocation(fakeLocation.first.toDouble(), fakeLocation.second.toDouble()) - _locationMode.value = if (fakeLocation in citiesRepository.citiesLocationsList) LocationMode.RANDOM_LOCATION + localStateRepository.locationMode.value = if (fakeLocation in citiesRepository.citiesLocationsList) LocationMode.RANDOM_LOCATION else LocationMode.SPECIFIC_LOCATION } else { fakeLocationModule.stopFakeLocation() - _locationMode.value = LocationMode.REAL_LOCATION + localStateRepository.locationMode.value = LocationMode.REAL_LOCATION } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt index db6f312..fd9430c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/GetQuickPrivacyStateUseCase.kt @@ -18,14 +18,63 @@ package foundation.e.privacycentralapp.domain.usecases import foundation.e.privacycentralapp.data.repositories.LocalStateRepository +import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode +import foundation.e.privacycentralapp.domain.entities.LocationMode +import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine class GetQuickPrivacyStateUseCase(private val localStateRepository: LocalStateRepository) { val quickPrivacyEnabledFlow = localStateRepository.quickPrivacyEnabledFlow val isQuickPrivacyEnabled get() = localStateRepository.isQuickPrivacyEnabled - fun toggle(): Boolean { + val quickPrivacyState = combine( + localStateRepository.quickPrivacyEnabledFlow, + localStateRepository.areAllTrackersBlocked, + localStateRepository.locationMode, + localStateRepository.internetPrivacyMode + ) { isQuickPrivacyEnabled, isAllTrackersBlocked, locationMode, internetPrivacyMode -> + when { + !isQuickPrivacyEnabled -> QuickPrivacyState.DISABLED + isAllTrackersBlocked && + locationMode != LocationMode.REAL_LOCATION && + internetPrivacyMode in listOf( + InternetPrivacyMode.HIDE_IP, + InternetPrivacyMode.HIDE_IP_LOADING + ) -> QuickPrivacyState.FULL_ENABLED + else -> QuickPrivacyState.ENABLED + } + } + + val isTrackersDenied = combine( + localStateRepository.quickPrivacyEnabledFlow, + localStateRepository.areAllTrackersBlocked + ) { isQuickPrivacyEnabled, isAllTrackersBlocked -> + isQuickPrivacyEnabled && isAllTrackersBlocked + } + + val isLocationHidden = combine( + localStateRepository.quickPrivacyEnabledFlow, + localStateRepository.locationMode + ) { isQuickPrivacyEnabled, locationMode -> + isQuickPrivacyEnabled && locationMode != LocationMode.REAL_LOCATION + } + + val locationMode: StateFlow = localStateRepository.locationMode + + val isIpHidden = combine( + localStateRepository.quickPrivacyEnabledFlow, + localStateRepository.internetPrivacyMode + ) { isQuickPrivacyEnabled, internetPrivacyMode -> + when { + !isQuickPrivacyEnabled || internetPrivacyMode == InternetPrivacyMode.REAL_IP -> false + internetPrivacyMode == InternetPrivacyMode.HIDE_IP -> true + else -> null + } + } + + fun toggleReturnIsFirstActivation(): Boolean { val newState = !localStateRepository.isQuickPrivacyEnabled - localStateRepository.isQuickPrivacyEnabled = newState - return newState + return localStateRepository.setQuickPrivacyReturnIsFirstActivation(newState) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt index 6cc8e4a..a701eec 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt @@ -71,6 +71,10 @@ class IpScramblingStateUseCase( applySettings(it, localStateRepository.isIpScramblingEnabled) } } + + coroutineScope.launch { + internetPrivacyMode.collect { localStateRepository.internetPrivacyMode.value = it } + } } fun toggle(hideIp: Boolean) { @@ -119,7 +123,7 @@ class IpScramblingStateUseCase( private fun applySettings(isQuickPrivacyEnabled: Boolean, isIpScramblingEnabled: Boolean) { when { - isQuickPrivacyEnabled && isIpScramblingEnabled -> when (internetPrivacyMode.value) { + isQuickPrivacyEnabled && isIpScramblingEnabled -> when (localStateRepository.internetPrivacyMode.value) { InternetPrivacyMode.REAL_IP, InternetPrivacyMode.REAL_IP_LOADING -> { val intent = ipScramblerModule.prepareAndroidVpn() if (intent != null) { @@ -129,7 +133,7 @@ class IpScramblingStateUseCase( } else -> {} } - else -> when (internetPrivacyMode.value) { + else -> when (localStateRepository.internetPrivacyMode.value) { InternetPrivacyMode.HIDE_IP, InternetPrivacyMode.HIDE_IP_LOADING -> ipScramblerModule.stop() else -> {} } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt index a589509..5263559 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt @@ -26,9 +26,6 @@ import foundation.e.privacymodules.trackers.IBlockTrackersPrivacyModule import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule import foundation.e.privacymodules.trackers.Tracker import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch @@ -41,13 +38,6 @@ class TrackersStateUseCase( private val appListsRepository: AppListsRepository, private val coroutineScope: CoroutineScope ) { - - private val _areAllTrackersBlocked = MutableStateFlow( - blockTrackersPrivacyModule.isBlockingEnabled() && - blockTrackersPrivacyModule.isWhiteListEmpty() - ) - val areAllTrackersBlocked: StateFlow = _areAllTrackersBlocked - init { trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false) coroutineScope.launch { @@ -63,7 +53,7 @@ class TrackersStateUseCase( } private fun updateAllTrackersBlockedState() { - _areAllTrackersBlocked.value = blockTrackersPrivacyModule.isBlockingEnabled() && + localStateRepository.areAllTrackersBlocked.value = blockTrackersPrivacyModule.isBlockingEnabled() && blockTrackersPrivacyModule.isWhiteListEmpty() } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt index ca45393..95726db 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt @@ -22,14 +22,13 @@ 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.entities.InternetPrivacyMode +import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.domain.entities.LocationMode -import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase +import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase -import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase -import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -49,10 +48,11 @@ class DashboardFeature( singleEventProducer ) { data class State( - val isQuickPrivacyEnabled: Boolean = false, - val isAllTrackersBlocked: Boolean = false, + val quickPrivacyState: QuickPrivacyState = QuickPrivacyState.DISABLED, + val isTrackersDenied: Boolean = false, + val isLocationHidden: Boolean = false, + val isIpHidden: Boolean? = false, val locationMode: LocationMode = LocationMode.REAL_LOCATION, - val internetPrivacyMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP, val leakedTrackersCount: Int? = null, val trackersCount: Int? = null, val activeTrackersCount: Int? = null, @@ -66,6 +66,7 @@ class DashboardFeature( object NavigateToLocationSingleEvent : SingleEvent() object NavigateToPermissionsSingleEvent : SingleEvent() object NewStatisticsAvailableSingleEvent : SingleEvent() + data class ToastMessageSingleEvent(val message: Int) : SingleEvent() } sealed class Action { @@ -80,8 +81,8 @@ class DashboardFeature( sealed class Effect { object NoEffect : Effect() - data class UpdateStateEffect(val isEnabled: Boolean) : Effect() - data class IpScramblingModeUpdatedEffect(val mode: InternetPrivacyMode) : Effect() + data class UpdateStateEffect(val state: QuickPrivacyState) : Effect() + data class IpScramblingModeUpdatedEffect(val isIpHidden: Boolean?) : Effect() data class TrackersStatisticsUpdatedEffect( val dayStatistics: List>, val dayLabels: List, @@ -96,24 +97,23 @@ class DashboardFeature( object OpenAppsPermissionsEffect : Effect() object OpenTrackersEffect : Effect() object NewStatisticsAvailablesEffect : Effect() + object FirstIPTrackerActivationEffect : Effect() + data class LocationHiddenUpdatedEffect(val isLocationHidden: Boolean) : Effect() } companion object { fun create( coroutineScope: CoroutineScope, getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - ipScramblingStateUseCase: IpScramblingStateUseCase, trackersStatisticsUseCase: TrackersStatisticsUseCase, - trackersStateUseCase: TrackersStateUseCase, - fakeLocationStateUseCase: FakeLocationStateUseCase ): DashboardFeature = DashboardFeature( initialState = State(), coroutineScope, reducer = { state, effect -> when (effect) { - is Effect.UpdateStateEffect -> state.copy(isQuickPrivacyEnabled = effect.isEnabled) - is Effect.IpScramblingModeUpdatedEffect -> state.copy(internetPrivacyMode = effect.mode) + is Effect.UpdateStateEffect -> state.copy(quickPrivacyState = effect.state) + is Effect.IpScramblingModeUpdatedEffect -> state.copy(isIpHidden = effect.isIpHidden) is Effect.TrackersStatisticsUpdatedEffect -> state.copy( dayStatistics = effect.dayStatistics, dayLabels = effect.dayLabels, @@ -123,7 +123,10 @@ class DashboardFeature( ) is Effect.TrackersBlockedUpdatedEffect -> state.copy( - isAllTrackersBlocked = effect.areAllTrackersBlocked + isTrackersDenied = effect.areAllTrackersBlocked + ) + is Effect.LocationHiddenUpdatedEffect -> state.copy( + isLocationHidden = effect.isLocationHidden ) is Effect.UpdateLocationModeEffect -> state.copy(locationMode = effect.mode) @@ -133,24 +136,30 @@ class DashboardFeature( actor = { _: State, action: Action -> when (action) { Action.TogglePrivacyAction -> { - getPrivacyStateUseCase.toggle() - flowOf(Effect.NewStatisticsAvailablesEffect) + val isFirstActivation = getPrivacyStateUseCase.toggleReturnIsFirstActivation() + flow { + emit(Effect.NewStatisticsAvailablesEffect) + if (isFirstActivation) emit(Effect.FirstIPTrackerActivationEffect) + } } Action.InitAction -> merge( - getPrivacyStateUseCase.quickPrivacyEnabledFlow.map { + getPrivacyStateUseCase.quickPrivacyState.map { Effect.UpdateStateEffect(it) }, - ipScramblingStateUseCase.internetPrivacyMode.map { + getPrivacyStateUseCase.isIpHidden.map { Effect.IpScramblingModeUpdatedEffect(it) }, trackersStatisticsUseCase.listenUpdates().map { Effect.NewStatisticsAvailablesEffect }, - trackersStateUseCase.areAllTrackersBlocked.map { + getPrivacyStateUseCase.isTrackersDenied.map { Effect.TrackersBlockedUpdatedEffect(it) }, - fakeLocationStateUseCase.locationMode.map { + getPrivacyStateUseCase.isLocationHidden.map { + Effect.LocationHiddenUpdatedEffect(it) + }, + getPrivacyStateUseCase.locationMode.map { Effect.UpdateLocationModeEffect(it) } ) @@ -188,6 +197,10 @@ class DashboardFeature( SingleEvent.NavigateToTrackersSingleEvent is Effect.NewStatisticsAvailablesEffect -> SingleEvent.NewStatisticsAvailableSingleEvent + is Effect.FirstIPTrackerActivationEffect -> + SingleEvent.ToastMessageSingleEvent( + message = R.string.dashboard_first_ipscrambling_activation + ) else -> null } } 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 96ace56..588eedd 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 @@ -22,6 +22,7 @@ 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.getColor import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels @@ -35,8 +36,8 @@ import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.GraphHolder import foundation.e.privacycentralapp.common.NavToolbarFragment import foundation.e.privacycentralapp.databinding.FragmentDashboardBinding -import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode import foundation.e.privacycentralapp.domain.entities.LocationMode +import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf import foundation.e.privacycentralapp.features.dashboard.DashboardFeature.State import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyFragment @@ -104,6 +105,9 @@ class DashboardFragment : DashboardFeature.SingleEvent.NewStatisticsAvailableSingleEvent -> { viewModel.submitAction(DashboardFeature.Action.FetchStatistics) } + is DashboardFeature.SingleEvent.ToastMessageSingleEvent -> + Toast.makeText(requireContext(), event.message, Toast.LENGTH_LONG) + .show() } } } @@ -159,50 +163,48 @@ class DashboardFragment : override fun render(state: State) { binding.stateLabel.text = getString( - if (state.isQuickPrivacyEnabled) R.string.dashboard_state_title_on - else R.string.dashboard_state_title_off + 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.stateIcon.setImageResource( - if (state.isQuickPrivacyEnabled) R.drawable.ic_shield_on + if (state.quickPrivacyState.isEnabled()) R.drawable.ic_shield_on else R.drawable.ic_shield_off ) - binding.togglePrivacyCentral.isChecked = state.isQuickPrivacyEnabled + binding.togglePrivacyCentral.isChecked = state.quickPrivacyState.isEnabled() - val trackersEnabled = state.isQuickPrivacyEnabled && state.isAllTrackersBlocked binding.stateTrackers.text = getString( - if (trackersEnabled) R.string.dashboard_state_trackers_on + if (state.isTrackersDenied) R.string.dashboard_state_trackers_on else R.string.dashboard_state_trackers_off ) binding.stateTrackers.setTextColor( getColor( requireContext(), - if (trackersEnabled) R.color.green_valid + if (state.isTrackersDenied) R.color.green_valid else R.color.red_off ) ) - val geolocEnabled = state.isQuickPrivacyEnabled && state.locationMode != LocationMode.REAL_LOCATION binding.stateGeolocation.text = getString( - if (geolocEnabled) R.string.dashboard_state_geolocation_on + if (state.isLocationHidden) R.string.dashboard_state_geolocation_on else R.string.dashboard_state_geolocation_off ) binding.stateGeolocation.setTextColor( getColor( requireContext(), - if (geolocEnabled) R.color.green_valid + if (state.isLocationHidden) R.color.green_valid else R.color.red_off ) ) - val ipAddressEnabled = state.isQuickPrivacyEnabled && state.internetPrivacyMode != InternetPrivacyMode.REAL_IP - val isLoading = state.isQuickPrivacyEnabled && state.internetPrivacyMode in listOf( - InternetPrivacyMode.HIDE_IP_LOADING, - InternetPrivacyMode.REAL_IP_LOADING - ) + val isLoading = state.isIpHidden == null + binding.stateIpAddress.text = getString( - if (ipAddressEnabled) R.string.dashboard_state_ipaddress_on + if (state.isIpHidden == true) R.string.dashboard_state_ipaddress_on else R.string.dashboard_state_ipaddress_off ) @@ -212,7 +214,7 @@ class DashboardFragment : binding.stateIpAddress.setTextColor( getColor( requireContext(), - if (ipAddressEnabled) R.color.green_valid + if (state.isIpHidden == true) R.color.green_valid else R.color.red_off ) ) @@ -252,10 +254,7 @@ class DashboardFragment : ) binding.internetActivityPrivacy.subTitle = getString( - if (state.isQuickPrivacyEnabled && - state.internetPrivacyMode != InternetPrivacyMode.REAL_IP - ) - R.string.dashboard_internet_activity_privacy_subtitle_on + if (state.isIpHidden == true) R.string.dashboard_internet_activity_privacy_subtitle_on else R.string.dashboard_internet_activity_privacy_subtitle_off ) diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt index 0dbcdda..ffd7951 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt @@ -20,10 +20,7 @@ package foundation.e.privacycentralapp.features.dashboard import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import foundation.e.privacycentralapp.common.Factory -import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase -import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase -import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow @@ -31,10 +28,7 @@ import kotlinx.coroutines.launch class DashboardViewModel( private val getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - private val ipScramblingStateUseCase: IpScramblingStateUseCase, private val trackersStatisticsUseCase: TrackersStatisticsUseCase, - private val trackersStateUseCase: TrackersStateUseCase, - private val fakeLocationStateUseCase: FakeLocationStateUseCase ) : ViewModel() { private val _actions = MutableSharedFlow() @@ -44,10 +38,7 @@ class DashboardViewModel( DashboardFeature.create( coroutineScope = viewModelScope, getPrivacyStateUseCase = getPrivacyStateUseCase, - ipScramblingStateUseCase = ipScramblingStateUseCase, trackersStatisticsUseCase = trackersStatisticsUseCase, - trackersStateUseCase = trackersStateUseCase, - fakeLocationStateUseCase = fakeLocationStateUseCase ) } @@ -60,12 +51,9 @@ class DashboardViewModel( class DashBoardViewModelFactory( private val getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - private val ipScramblingStateUseCase: IpScramblingStateUseCase, private val trackersStatisticsUseCase: TrackersStatisticsUseCase, - private val trackersStateUseCase: TrackersStateUseCase, - private val fakeLocationStateUseCase: FakeLocationStateUseCase ) : Factory { override fun create(): DashboardViewModel { - return DashboardViewModel(getPrivacyStateUseCase, ipScramblingStateUseCase, trackersStatisticsUseCase, trackersStateUseCase, fakeLocationStateUseCase) + return DashboardViewModel(getPrivacyStateUseCase, trackersStatisticsUseCase) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt index 1969fe5..7efb6b0 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt @@ -20,10 +20,7 @@ package foundation.e.privacycentralapp import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context -import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase -import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase -import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import foundation.e.privacycentralapp.widget.State import foundation.e.privacycentralapp.widget.render @@ -72,25 +69,22 @@ class Widget : AppWidgetProvider() { private fun initState( getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - ipScramblingStateUseCase: IpScramblingStateUseCase, trackersStatisticsUseCase: TrackersStatisticsUseCase, - trackersStateUseCase: TrackersStateUseCase, - fakeLocationStateUseCase: FakeLocationStateUseCase, coroutineScope: CoroutineScope ): StateFlow { return combine( - getPrivacyStateUseCase.quickPrivacyEnabledFlow, - trackersStateUseCase.areAllTrackersBlocked, - fakeLocationStateUseCase.locationMode, - ipScramblingStateUseCase.internetPrivacyMode - ) { isQuickPrivacyEnabled, isAllTrackersBlocked, locationMode, internetPrivacyMode -> + getPrivacyStateUseCase.quickPrivacyState, + getPrivacyStateUseCase.isTrackersDenied, + getPrivacyStateUseCase.isLocationHidden, + getPrivacyStateUseCase.isIpHidden, + ) { quickPrivacyState, isTrackersDenied, isLocationHidden, isIpHidden -> State( - isQuickPrivacyEnabled = isQuickPrivacyEnabled, - isAllTrackersBlocked = isAllTrackersBlocked, - locationMode = locationMode, - internetPrivacyMode = internetPrivacyMode + quickPrivacyState = quickPrivacyState, + isTrackersDenied = isTrackersDenied, + isLocationHidden = isLocationHidden, + isIpHidden = isIpHidden ) }.sample(50) .combine( @@ -112,17 +106,11 @@ class Widget : AppWidgetProvider() { fun startListening( appContext: Context, getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - ipScramblingStateUseCase: IpScramblingStateUseCase, trackersStatisticsUseCase: TrackersStatisticsUseCase, - trackersStateUseCase: TrackersStateUseCase, - fakeLocationStateUseCase: FakeLocationStateUseCase ) { state = initState( getPrivacyStateUseCase, - ipScramblingStateUseCase, trackersStatisticsUseCase, - trackersStateUseCase, - fakeLocationStateUseCase, GlobalScope ) diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt index 87e88df..a4e3079 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt @@ -27,7 +27,7 @@ class WidgetCommandReceiver : BroadcastReceiver() { when (intent?.action) { ACTION_TOGGLE_PRIVACY -> { (context?.applicationContext as? PrivacyCentralApplication) - ?.dependencyContainer?.getQuickPrivacyStateUseCase?.toggle() + ?.dependencyContainer?.getQuickPrivacyStateUseCase?.toggleReturnIsFirstActivation() } else -> {} } diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt index 2529f6c..57ddd0c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt @@ -27,24 +27,20 @@ import android.view.View import android.widget.RemoteViews import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.Widget -import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode -import foundation.e.privacycentralapp.domain.entities.LocationMode +import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState import foundation.e.privacycentralapp.extensions.dpToPxF import foundation.e.privacycentralapp.main.MainActivity import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_PRIVACY import kotlinx.coroutines.FlowPreview data class State( - val isQuickPrivacyEnabled: Boolean = false, - val isAllTrackersBlocked: Boolean = false, - val locationMode: LocationMode = LocationMode.REAL_LOCATION, - val internetPrivacyMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP, + val quickPrivacyState: QuickPrivacyState = QuickPrivacyState.DISABLED, + val isTrackersDenied: Boolean = false, + val isLocationHidden: Boolean = false, + val isIpHidden: Boolean? = false, val dayStatistics: List> = emptyList(), val activeTrackersCount: Int = 0, -) { - val isTrackersDenied get() = isQuickPrivacyEnabled && isAllTrackersBlocked - val isLocationHidden get() = isQuickPrivacyEnabled && locationMode != LocationMode.REAL_LOCATION -} +) @FlowPreview fun render( @@ -62,18 +58,22 @@ fun render( setImageViewResource( R.id.state_icon, - if (state.isQuickPrivacyEnabled) R.drawable.ic_shield_on else R.drawable.ic_shield_off + if (state.quickPrivacyState.isEnabled()) R.drawable.ic_shield_on_white + else R.drawable.ic_shield_off_white ) setTextViewText( R.id.state_label, context.getString( - if (state.isQuickPrivacyEnabled) R.string.widget_state_title_on - else R.string.widget_state_title_off + when (state.quickPrivacyState) { + QuickPrivacyState.DISABLED -> R.string.widget_state_title_off + QuickPrivacyState.FULL_ENABLED -> R.string.widget_state_title_on + QuickPrivacyState.ENABLED -> R.string.widget_state_title_custom + } ) ) setImageViewResource( R.id.toggle_privacy_central, - if (state.isQuickPrivacyEnabled) R.drawable.ic_switch_enabled + if (state.quickPrivacyState.isEnabled()) R.drawable.ic_switch_enabled else R.drawable.ic_switch_disabled ) @@ -108,16 +108,12 @@ fun render( setTextViewText( R.id.state_ip_address, context.getString( - if (state.internetPrivacyMode != InternetPrivacyMode.HIDE_IP) - R.string.widget_state_ipaddress_off - else R.string.widget_state_ipaddress_on + if (state.isIpHidden == true) R.string.widget_state_ipaddress_on + else R.string.widget_state_ipaddress_off ) ) - val loading = state.internetPrivacyMode in listOf( - InternetPrivacyMode.HIDE_IP_LOADING, - InternetPrivacyMode.REAL_IP_LOADING - ) + val loading = state.isIpHidden == null setViewVisibility(R.id.state_ip_address, if (loading) View.GONE else View.VISIBLE) diff --git a/app/src/main/res/drawable-night/ic_shield_off.xml b/app/src/main/res/drawable-night/ic_shield_off.xml new file mode 100644 index 0000000..f45fd98 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_shield_off.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/app/src/main/res/drawable-night/ic_shield_on.xml b/app/src/main/res/drawable-night/ic_shield_on.xml new file mode 100644 index 0000000..ecc27b4 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_shield_on.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_shield_off.xml b/app/src/main/res/drawable/ic_shield_off.xml index 8f811a2..cd60ba4 100644 --- a/app/src/main/res/drawable/ic_shield_off.xml +++ b/app/src/main/res/drawable/ic_shield_off.xml @@ -1,16 +1,19 @@ + android:viewportWidth="25" + android:viewportHeight="24"> + + - + android:fillAlpha="0.87"/> + + diff --git a/app/src/main/res/drawable/ic_shield_off_white.xml b/app/src/main/res/drawable/ic_shield_off_white.xml new file mode 100644 index 0000000..f45fd98 --- /dev/null +++ b/app/src/main/res/drawable/ic_shield_off_white.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_shield_on.xml b/app/src/main/res/drawable/ic_shield_on.xml index b70dc6e..e29f766 100644 --- a/app/src/main/res/drawable/ic_shield_on.xml +++ b/app/src/main/res/drawable/ic_shield_on.xml @@ -1,17 +1,18 @@ + + - + android:pathData="M12.5,1.5L13.3123,-0.3276L12.5,-0.6886L11.6877,-0.3276L12.5,1.5ZM3.5,5.5L2.6877,3.6724L1.5,4.2003V5.5H3.5ZM12.5,23.5L12.0256,25.4429L12.5,25.5588L12.9744,25.4429L12.5,23.5ZM21.5,5.5H23.5V4.2003L22.3123,3.6724L21.5,5.5ZM11.6877,-0.3276L2.6877,3.6724L4.3123,7.3276L13.3123,3.3276L11.6877,-0.3276ZM1.5,5.5V11.5H5.5V5.5H1.5ZM1.5,11.5C1.5,17.8888 5.8919,23.9452 12.0256,25.4429L12.9744,21.5571C8.7881,20.5348 5.5,16.2112 5.5,11.5H1.5ZM12.9744,25.4429C19.1081,23.9452 23.5,17.8888 23.5,11.5H19.5C19.5,16.2112 16.2119,20.5348 12.0256,21.5571L12.9744,25.4429ZM23.5,11.5V5.5H19.5V11.5H23.5ZM22.3123,3.6724L13.3123,-0.3276L11.6877,3.3276L20.6877,7.3276L22.3123,3.6724Z" + android:fillColor="#000000" + android:fillAlpha="0.87"/> + + diff --git a/app/src/main/res/drawable/ic_shield_on_white.xml b/app/src/main/res/drawable/ic_shield_on_white.xml new file mode 100644 index 0000000..ecc27b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_shield_on_white.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cf293e1..1fc7841 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ @string/app_name Your online privacy is protected Your online privacy is unprotected + Custom privacy settings applied Trackers: Vulnerable Denied @@ -25,6 +26,7 @@ Personal data leakage: Today %s trackers have profiled you in the last 24 hours + While your IP is faked, your internet speed is likely to be reduced. Manage apps\' trackers %1$d app trackers, %2$d active trackers @@ -126,6 +128,7 @@ @string/app_name Your online privacy is protected Your online privacy is unprotected + Custom privacy settings applied @string/dashboard_state_trackers_label @string/dashboard_state_trackers_off @string/dashboard_state_trackers_on -- cgit v1.2.1