diff options
Diffstat (limited to 'app/src/main/java/foundation/e/privacycentralapp')
27 files changed, 219 insertions, 328 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index 670b81e..345307c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -82,7 +82,7 @@ class DependencyContainer(val app: Application) { // Usecases val getQuickPrivacyStateUseCase by lazy { - GetQuickPrivacyStateUseCase(localStateRepository, GlobalScope) + GetQuickPrivacyStateUseCase(localStateRepository) } private val ipScramblingStateUseCase by lazy { IpScramblingStateUseCase( diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/QuickPrivacyDisabledSnackbar.kt b/app/src/main/java/foundation/e/privacycentralapp/common/QuickPrivacyDisabledSnackbar.kt deleted file mode 100644 index 705f65d..0000000 --- a/app/src/main/java/foundation/e/privacycentralapp/common/QuickPrivacyDisabledSnackbar.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 <https://www.gnu.org/licenses/>. - */ - -package foundation.e.privacycentralapp.common - -import android.view.View -import com.google.android.material.snackbar.BaseTransientBottomBar -import com.google.android.material.snackbar.Snackbar -import foundation.e.privacycentralapp.R - -fun initQuickPrivacySnackbar(view: View, onDismiss: () -> Unit): Snackbar { - val snackbar = Snackbar.make(view, R.string.quickprivacy_disabled_message, Snackbar.LENGTH_INDEFINITE) - snackbar.setAction(R.string.close) { onDismiss() } - - snackbar.addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { - override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { - super.onDismissed(transientBottomBar, event) - if (event == DISMISS_EVENT_SWIPE) onDismiss() - } - }) - return snackbar -} 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 d39ee43..92ee0c1 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 @@ -24,14 +24,13 @@ import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update class LocalStateRepository(context: Context) { companion object { private const val SHARED_PREFS_FILE = "localState" - private const val KEY_QUICK_PRIVACY = "quickPrivacy" + private const val KEY_BLOCK_TRACKERS = "blockTrackers" private const val KEY_IP_SCRAMBLING = "ipScrambling" private const val KEY_FAKE_LOCATION = "fakeLocation" private const val KEY_FAKE_LATITUDE = "fakeLatitude" @@ -41,58 +40,48 @@ class LocalStateRepository(context: Context) { private val sharedPref = context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE) - private val quickPrivacyEnabledMutableFlow = - MutableStateFlow<Boolean>(sharedPref.getBoolean(KEY_QUICK_PRIVACY, false)) - val isQuickPrivacyEnabled: Boolean get() = quickPrivacyEnabledMutableFlow.value + private val _blockTrackers = MutableStateFlow(sharedPref.getBoolean(KEY_BLOCK_TRACKERS, true)) + val blockTrackers = _blockTrackers.asStateFlow() - fun setQuickPrivacyReturnIsFirstActivation(value: Boolean): Boolean { - val isFirstActivation = value && !sharedPref.contains(KEY_QUICK_PRIVACY) - set(KEY_QUICK_PRIVACY, value) - quickPrivacyEnabledMutableFlow.value = value - return isFirstActivation + fun setBlockTrackers(enabled: Boolean) { + set(KEY_BLOCK_TRACKERS, enabled) + _blockTrackers.update { enabled } } - private val _otherVpnRunning = MutableSharedFlow<ApplicationDescription>() - suspend fun emitOtherVpnRunning(appDesc: ApplicationDescription) { - _otherVpnRunning.emit(appDesc) - } + val areAllTrackersBlocked: MutableStateFlow<Boolean> = MutableStateFlow(false) - val otherVpnRunning: SharedFlow<ApplicationDescription> = _otherVpnRunning + private val _fakeLocationEnabled = MutableStateFlow(sharedPref.getBoolean(KEY_FAKE_LOCATION, true)) - var quickPrivacyEnabledFlow: StateFlow<Boolean> = quickPrivacyEnabledMutableFlow + val fakeLocationEnabled = _fakeLocationEnabled.asStateFlow() - val areAllTrackersBlocked: MutableStateFlow<Boolean> = MutableStateFlow(false) + fun setFakeLocationEnabled(enabled: Boolean) { + set(KEY_FAKE_LOCATION, enabled) + _fakeLocationEnabled.update { enabled } + } - var fakeLocation: Pair<Float, Float>? - get() = if (sharedPref.getBoolean(KEY_FAKE_LOCATION, true)) - Pair( - // Initial default value is Quezon City - sharedPref.getFloat(KEY_FAKE_LATITUDE, 14.6760f), - sharedPref.getFloat(KEY_FAKE_LONGITUDE, 121.0437f) - ) - else null + var fakeLocation: Pair<Float, Float> + get() = Pair( + // Initial default value is Quezon City + sharedPref.getFloat(KEY_FAKE_LATITUDE, 14.6760f), + sharedPref.getFloat(KEY_FAKE_LONGITUDE, 121.0437f) + ) 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() - } + sharedPref.edit() + .putFloat(KEY_FAKE_LATITUDE, value.first) + .putFloat(KEY_FAKE_LONGITUDE, value.second) + .apply() } val locationMode: MutableStateFlow<LocationMode> = MutableStateFlow(LocationMode.REAL_LOCATION) - private val _ipScramblingSetting = MutableStateFlow(sharedPref.getBoolean(KEY_IP_SCRAMBLING, true)) + private val _ipScramblingSetting = MutableStateFlow(sharedPref.getBoolean(KEY_IP_SCRAMBLING, false)) val ipScramblingSetting = _ipScramblingSetting.asStateFlow() + fun isIpScramblingFirstActivation(enabled: Boolean): Boolean { + return enabled && !sharedPref.contains(KEY_IP_SCRAMBLING) + } + fun setIpScramblingSetting(enabled: Boolean) { set(KEY_IP_SCRAMBLING, enabled) _ipScramblingSetting.update { enabled } @@ -100,18 +89,17 @@ class LocalStateRepository(context: Context) { val internetPrivacyMode: MutableStateFlow<InternetPrivacyMode> = MutableStateFlow(InternetPrivacyMode.REAL_IP) - private val _showQuickPrivacyDisabledMessage = MutableStateFlow(false) - val showQuickPrivacyDisabledMessage: StateFlow<Boolean> = _showQuickPrivacyDisabledMessage - - fun setShowQuickPrivacyDisabledMessage(show: Boolean) { - _showQuickPrivacyDisabledMessage.value = show + private val _otherVpnRunning = MutableSharedFlow<ApplicationDescription>() + suspend fun emitOtherVpnRunning(appDesc: ApplicationDescription) { + _otherVpnRunning.emit(appDesc) } + val otherVpnRunning: SharedFlow<ApplicationDescription> = _otherVpnRunning var firstBoot: Boolean get() = sharedPref.getBoolean(KEY_FIRST_BOOT, true) set(value) = set(KEY_FIRST_BOOT, value) private fun set(key: String, value: Boolean) { - sharedPref.edit().putBoolean(key, value).commit() + sharedPref.edit().putBoolean(key, value).apply() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/InternetPrivacyMode.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/InternetPrivacyMode.kt index 534bb2f..f849d57 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/InternetPrivacyMode.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/InternetPrivacyMode.kt @@ -21,5 +21,9 @@ enum class InternetPrivacyMode { REAL_IP, HIDE_IP, HIDE_IP_LOADING, - REAL_IP_LOADING + REAL_IP_LOADING; + + val isChecked get() = this == HIDE_IP || this == HIDE_IP_LOADING + + val isLoading get() = this == HIDE_IP_LOADING || this == REAL_IP_LOADING } 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 e9da855..2910f26 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 @@ -57,7 +57,7 @@ class FakeLocationStateUseCase( init { coroutineScope.launch { - localStateRepository.quickPrivacyEnabledFlow.collect { + localStateRepository.fakeLocationEnabled.collect { applySettings(it, localStateRepository.fakeLocation) } } @@ -71,10 +71,10 @@ class FakeLocationStateUseCase( permissionsModule.toggleDangerousPermission(appDesc, android.Manifest.permission.ACCESS_FINE_LOCATION, true) } - private fun applySettings(isQuickPrivacyEnabled: Boolean, fakeLocation: Pair<Float, Float>?, isSpecificLocation: Boolean = false) { - _configuredLocationMode.value = computeLocationMode(fakeLocation, isSpecificLocation) + private fun applySettings(isEnabled: Boolean, fakeLocation: Pair<Float, Float>, isSpecificLocation: Boolean = false) { + _configuredLocationMode.value = computeLocationMode(isEnabled, fakeLocation, isSpecificLocation) - if (isQuickPrivacyEnabled && fakeLocation != null && hasAcquireMockLocationPermission()) { + if (isEnabled && hasAcquireMockLocationPermission()) { fakeLocationModule.startFakeLocation() fakeLocationModule.setFakeLocation(fakeLocation.first.toDouble(), fakeLocation.second.toDouble()) localStateRepository.locationMode.value = configuredLocationMode.value.first @@ -90,18 +90,10 @@ class FakeLocationStateUseCase( } fun setSpecificLocation(latitude: Float, longitude: Float) { - if (!localStateRepository.isQuickPrivacyEnabled) { - localStateRepository.setShowQuickPrivacyDisabledMessage(true) - } - setFakeLocation(latitude to longitude, true) } fun setRandomLocation() { - if (!localStateRepository.isQuickPrivacyEnabled) { - localStateRepository.setShowQuickPrivacyDisabledMessage(true) - } - val randomIndex = Random.nextInt(citiesRepository.citiesLocationsList.size) val location = citiesRepository.citiesLocationsList[randomIndex] @@ -110,26 +102,29 @@ class FakeLocationStateUseCase( private fun setFakeLocation(location: Pair<Float, Float>, isSpecificLocation: Boolean = false) { localStateRepository.fakeLocation = location - applySettings(localStateRepository.isQuickPrivacyEnabled, location, isSpecificLocation) + localStateRepository.setFakeLocationEnabled(true) + applySettings(true, location, isSpecificLocation) } fun stopFakeLocation() { - if (!localStateRepository.isQuickPrivacyEnabled) { - localStateRepository.setShowQuickPrivacyDisabledMessage(true) - } - - localStateRepository.fakeLocation = null - applySettings(localStateRepository.isQuickPrivacyEnabled, null) + localStateRepository.setFakeLocationEnabled(false) + applySettings(false, localStateRepository.fakeLocation) } - private fun computeLocationMode(fakeLocation: Pair<Float, Float>?, isSpecificLocation: Boolean = false): Triple<LocationMode, Float?, Float?> { + private fun computeLocationMode( + isFakeLocationEnabled: Boolean, + fakeLocation: Pair<Float, Float>, + isSpecificLocation: Boolean = false, + ): Triple<LocationMode, Float?, Float?> { return Triple( when { - fakeLocation == null -> LocationMode.REAL_LOCATION - fakeLocation in citiesRepository.citiesLocationsList && !isSpecificLocation -> LocationMode.RANDOM_LOCATION + !isFakeLocationEnabled -> LocationMode.REAL_LOCATION + (fakeLocation in citiesRepository.citiesLocationsList && !isSpecificLocation) -> + LocationMode.RANDOM_LOCATION else -> LocationMode.SPECIFIC_LOCATION }, - fakeLocation?.first, fakeLocation?.second + fakeLocation.first, + fakeLocation.second ) } 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 46e054e..85410d0 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 @@ -23,88 +23,69 @@ import foundation.e.privacycentralapp.domain.entities.LocationMode import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState import foundation.e.privacycentralapp.domain.entities.TrackerMode import foundation.e.privacymodules.permissions.data.ApplicationDescription -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.map class GetQuickPrivacyStateUseCase( - private val localStateRepository: LocalStateRepository, - coroutineScope: CoroutineScope + private val localStateRepository: LocalStateRepository ) { - - init { - coroutineScope.launch { - localStateRepository.quickPrivacyEnabledFlow.collect { - if (it) resetQuickPrivacyDisabledMessage() - } - } - } - - val quickPrivacyEnabledFlow: Flow<Boolean> = localStateRepository.quickPrivacyEnabledFlow - - val isQuickPrivacyEnabled: Boolean get() = localStateRepository.isQuickPrivacyEnabled - val quickPrivacyState: Flow<QuickPrivacyState> = combine( - localStateRepository.quickPrivacyEnabledFlow, + localStateRepository.blockTrackers, localStateRepository.areAllTrackersBlocked, localStateRepository.locationMode, localStateRepository.internetPrivacyMode - ) { isQuickPrivacyEnabled, isAllTrackersBlocked, locationMode, internetPrivacyMode -> + ) { isBlockTrackers, isAllTrackersBlocked, locationMode, internetPrivacyMode -> when { - !isQuickPrivacyEnabled -> QuickPrivacyState.DISABLED + !isBlockTrackers && + locationMode == LocationMode.REAL_LOCATION && + internetPrivacyMode == InternetPrivacyMode.REAL_IP -> QuickPrivacyState.DISABLED + isAllTrackersBlocked && locationMode != LocationMode.REAL_LOCATION && internetPrivacyMode in listOf( InternetPrivacyMode.HIDE_IP, InternetPrivacyMode.HIDE_IP_LOADING ) -> QuickPrivacyState.FULL_ENABLED + else -> QuickPrivacyState.ENABLED } } val trackerMode: Flow<TrackerMode> = combine( - localStateRepository.quickPrivacyEnabledFlow, + localStateRepository.blockTrackers, localStateRepository.areAllTrackersBlocked - ) { isQuickPrivacyEnabled, isAllTrackersBlocked -> + ) { isBlockTrackers, isAllTrackersBlocked -> when { - isQuickPrivacyEnabled && isAllTrackersBlocked -> TrackerMode.DENIED - isQuickPrivacyEnabled && !isAllTrackersBlocked -> TrackerMode.CUSTOM + isBlockTrackers && isAllTrackersBlocked -> TrackerMode.DENIED + isBlockTrackers && !isAllTrackersBlocked -> TrackerMode.CUSTOM else -> TrackerMode.VULNERABLE } } - val isLocationHidden: Flow<Boolean> = combine( - localStateRepository.quickPrivacyEnabledFlow, - localStateRepository.locationMode - ) { isQuickPrivacyEnabled, locationMode -> - isQuickPrivacyEnabled && locationMode != LocationMode.REAL_LOCATION + val isLocationHidden: Flow<Boolean> = localStateRepository.locationMode.map { locationMode -> + locationMode != LocationMode.REAL_LOCATION } val locationMode: StateFlow<LocationMode> = localStateRepository.locationMode - val isIpHidden: Flow<Boolean?> = combine( - localStateRepository.quickPrivacyEnabledFlow, - localStateRepository.internetPrivacyMode - ) { isQuickPrivacyEnabled, internetPrivacyMode -> - when { - !isQuickPrivacyEnabled || internetPrivacyMode == InternetPrivacyMode.REAL_IP -> false - internetPrivacyMode == InternetPrivacyMode.HIDE_IP -> true - else -> null - } - } + val ipScramblingMode: Flow<InternetPrivacyMode> = localStateRepository.internetPrivacyMode - fun toggleReturnIsFirstActivation(): Boolean { - val newState = !localStateRepository.isQuickPrivacyEnabled - return localStateRepository.setQuickPrivacyReturnIsFirstActivation(newState) + fun toggleTrackers() { + localStateRepository.setBlockTrackers(!localStateRepository.blockTrackers.value) } - val showQuickPrivacyDisabledMessage: StateFlow<Boolean> = localStateRepository.showQuickPrivacyDisabledMessage + fun toggleLocation() { + localStateRepository.setFakeLocationEnabled(!localStateRepository.fakeLocationEnabled.value) + } - fun resetQuickPrivacyDisabledMessage() { - localStateRepository.setShowQuickPrivacyDisabledMessage(false) + fun toggleIpScramblingIsFirstActivation(): Boolean { + val enabled = !localStateRepository.ipScramblingSetting.value + val firstActivation = localStateRepository.isIpScramblingFirstActivation(enabled) + localStateRepository.setIpScramblingSetting(enabled) + return firstActivation } val otherVpnRunning: SharedFlow<ApplicationDescription> = localStateRepository.otherVpnRunning 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 cb9fcd5..9216233 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 @@ -28,6 +28,7 @@ import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule import foundation.e.privacymodules.permissions.IPermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -43,9 +44,6 @@ class IpScramblingStateUseCase( private val appListsRepository: AppListsRepository, private val coroutineScope: CoroutineScope ) { - - val configuredMode: StateFlow<Boolean> = localStateRepository.ipScramblingSetting - val internetPrivacyMode: StateFlow<InternetPrivacyMode> = callbackFlow { val listener = object : IIpScramblerModule.Listener { override fun onStatusChanged(newStatus: IIpScramblerModule.Status) { @@ -67,13 +65,13 @@ class IpScramblingStateUseCase( }.stateIn( scope = coroutineScope, started = SharingStarted.Eagerly, - initialValue = InternetPrivacyMode.REAL_IP + initialValue = REAL_IP ) init { - coroutineScope.launch { - localStateRepository.quickPrivacyEnabledFlow.collect { - applySettings(it, localStateRepository.ipScramblingSetting.value) + coroutineScope.launch(Dispatchers.Default) { + localStateRepository.ipScramblingSetting.collect { + applySettings(it) } } @@ -84,12 +82,7 @@ class IpScramblingStateUseCase( fun toggle(hideIp: Boolean) { localStateRepository.setIpScramblingSetting(enabled = hideIp) - - if (!localStateRepository.isQuickPrivacyEnabled) { - localStateRepository.setShowQuickPrivacyDisabledMessage(true) - } else { - applySettings(true, hideIp) - } + applySettings(hideIp) } private fun getHiddenPackageNames(): List<String> { @@ -129,15 +122,13 @@ class IpScramblingStateUseCase( ipScramblerModule.appList = rawList } - private fun applySettings(isQuickPrivacyEnabled: Boolean, isIpScramblingEnabled: Boolean) { - val settingEnabled = isQuickPrivacyEnabled && isIpScramblingEnabled + private fun applySettings(isIpScramblingEnabled: Boolean) { val currentMode = localStateRepository.internetPrivacyMode.value - when { - settingEnabled && currentMode in setOf(REAL_IP, REAL_IP_LOADING) -> + isIpScramblingEnabled && currentMode in setOf(REAL_IP, REAL_IP_LOADING) -> applyStartIpScrambling() - !settingEnabled && currentMode in setOf(HIDE_IP, HIDE_IP_LOADING) -> + !isIpScramblingEnabled && currentMode in setOf(HIDE_IP, HIDE_IP_LOADING) -> ipScramblerModule.stop() else -> {} @@ -162,11 +153,11 @@ class IpScramblingStateUseCase( private fun map(status: IIpScramblerModule.Status): InternetPrivacyMode { return when (status) { - IIpScramblerModule.Status.OFF -> InternetPrivacyMode.REAL_IP - IIpScramblerModule.Status.ON -> InternetPrivacyMode.HIDE_IP - IIpScramblerModule.Status.STARTING -> InternetPrivacyMode.HIDE_IP_LOADING + IIpScramblerModule.Status.OFF -> REAL_IP + IIpScramblerModule.Status.ON -> HIDE_IP + IIpScramblerModule.Status.STARTING -> HIDE_IP_LOADING IIpScramblerModule.Status.STOPPING, - IIpScramblerModule.Status.START_DISABLED -> InternetPrivacyMode.REAL_IP_LOADING + IIpScramblerModule.Status.START_DISABLED -> REAL_IP_LOADING } } } 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 17e5096..8b37152 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 @@ -38,7 +38,7 @@ class TrackersStateUseCase( init { trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false) coroutineScope.launch { - localStateRepository.quickPrivacyEnabledFlow.collect { enabled -> + localStateRepository.blockTrackers.collect { enabled -> if (enabled) { blockTrackersPrivacyModule.enableBlocking() } else { @@ -76,10 +76,6 @@ class TrackersStateUseCase( } fun toggleAppWhitelist(appUid: Int, isWhitelisted: Boolean) { - if (!localStateRepository.isQuickPrivacyEnabled) { - localStateRepository.setShowQuickPrivacyDisabledMessage(true) - } - if (appUid == appListsRepository.dummySystemApp.uid) { appListsRepository.getHiddenSystemApps().forEach { blockTrackersPrivacyModule.setWhiteListed(it.uid, isWhitelisted) @@ -90,9 +86,6 @@ class TrackersStateUseCase( } fun blockTracker(appUid: Int, tracker: Tracker, isBlocked: Boolean) { - if (!localStateRepository.isQuickPrivacyEnabled) { - localStateRepository.setShowQuickPrivacyDisabledMessage(true) - } if (appUid == appListsRepository.dummySystemApp.uid) { appListsRepository.getHiddenSystemApps().forEach { blockTrackersPrivacyModule.setWhiteListed(tracker, it.uid, !isBlocked) 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 6cd259e..8a0a3d4 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 @@ -32,14 +32,13 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.google.android.material.snackbar.Snackbar import foundation.e.privacycentralapp.DependencyContainer import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.GraphHolder import foundation.e.privacycentralapp.common.NavToolbarFragment -import foundation.e.privacycentralapp.common.initQuickPrivacySnackbar 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.domain.entities.TrackerMode @@ -72,8 +71,6 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { private var _binding: FragmentDashboardBinding? = null private val binding get() = _binding!! - private var qpDisabledSnackbar: Snackbar? = null - private var highlightIndexOnStart: Int? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -91,8 +88,14 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { binding.leakingAppButton.setOnClickListener { viewModel.submitAction(Action.ShowMostLeakedApp) } - binding.togglePrivacyCentral.setOnClickListener { - viewModel.submitAction(Action.TogglePrivacyAction) + binding.toggleTrackers.setOnClickListener { + viewModel.submitAction(Action.ToggleTrackers) + } + binding.toggleLocation.setOnClickListener { + viewModel.submitAction(Action.ToggleLocation) + } + binding.toggleIpscrambling.setOnClickListener { + viewModel.submitAction(Action.ToggleIpScrambling) } binding.myLocation.container.setOnClickListener { viewModel.submitAction(Action.ShowFakeMyLocationAction) @@ -108,10 +111,6 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { viewModel.submitAction(Action.ShowTrackers) } - qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) { - viewModel.submitAction(Action.CloseQuickPrivacyDisabledMessage) - } - viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { render(viewModel.state.value) @@ -185,9 +184,6 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { } private fun render(state: DashboardState) { - if (state.showQuickPrivacyDisabledMessage) qpDisabledSnackbar?.show() - else qpDisabledSnackbar?.dismiss() - binding.stateLabel.text = getString( when (state.quickPrivacyState) { QuickPrivacyState.DISABLED -> R.string.dashboard_state_title_off @@ -201,7 +197,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { else R.drawable.ic_shield_off ) - binding.togglePrivacyCentral.isChecked = state.quickPrivacyState.isEnabled() + binding.toggleTrackers.isChecked = state.trackerMode != TrackerMode.VULNERABLE binding.stateTrackers.text = getString( when (state.trackerMode) { @@ -218,6 +214,8 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { ) ) + 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 @@ -230,10 +228,11 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { ) ) - val isLoading = state.isIpHidden == null + binding.toggleIpscrambling.isChecked = state.ipScramblingMode.isChecked + val isLoading = state.ipScramblingMode.isLoading binding.stateIpAddress.text = getString( - if (state.isIpHidden == true) R.string.dashboard_state_ipaddress_on + if (state.ipScramblingMode == InternetPrivacyMode.HIDE_IP) R.string.dashboard_state_ipaddress_on else R.string.dashboard_state_ipaddress_off ) @@ -243,7 +242,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { binding.stateIpAddress.setTextColor( getColor( requireContext(), - if (state.isIpHidden == true) R.color.green_valid + if (state.ipScramblingMode == InternetPrivacyMode.HIDE_IP) R.color.green_valid else R.color.red_off ) ) @@ -292,7 +291,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { ) binding.internetActivityPrivacy.subTitle = getString( - if (state.isIpHidden == true) R.string.dashboard_internet_activity_privacy_subtitle_on + if (state.ipScramblingMode == InternetPrivacyMode.HIDE_IP) R.string.dashboard_internet_activity_privacy_subtitle_on else R.string.dashboard_internet_activity_privacy_subtitle_off ) @@ -301,7 +300,6 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { override fun onDestroyView() { super.onDestroyView() - qpDisabledSnackbar = null graphHolder = null _binding = null } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt index 04b7ae8..937fa22 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardState.kt @@ -17,6 +17,7 @@ package foundation.e.privacycentralapp.features.dashboard +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.domain.entities.TrackerMode @@ -25,12 +26,11 @@ data class DashboardState( val quickPrivacyState: QuickPrivacyState = QuickPrivacyState.DISABLED, val trackerMode: TrackerMode = TrackerMode.VULNERABLE, val isLocationHidden: Boolean = false, - val isIpHidden: Boolean? = false, + val ipScramblingMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP_LOADING, val locationMode: LocationMode = LocationMode.REAL_LOCATION, val leakedTrackersCount: Int? = null, val trackersCount: Int? = null, val allowedTrackersCount: Int? = null, val dayStatistics: List<Pair<Int, Int>>? = null, val dayLabels: List<String>? = null, - val showQuickPrivacyDisabledMessage: Boolean = false ) 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 d7d74c6..57e7790 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 @@ -25,6 +25,7 @@ import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCas import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -58,8 +59,8 @@ class DashboardViewModel( getPrivacyStateUseCase.quickPrivacyState.map { _state.update { s -> s.copy(quickPrivacyState = it) } }, - getPrivacyStateUseCase.isIpHidden.map { - _state.update { s -> s.copy(isIpHidden = it) } + getPrivacyStateUseCase.ipScramblingMode.map { + _state.update { s -> s.copy(ipScramblingMode = it) } }, trackersStatisticsUseCase.listenUpdates().flatMapLatest { fetchStatistics() @@ -73,9 +74,6 @@ class DashboardViewModel( getPrivacyStateUseCase.locationMode.map { _state.update { s -> s.copy(locationMode = it) } }, - getPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map { - _state.update { s -> s.copy(showQuickPrivacyDisabledMessage = it) } - }, getPrivacyStateUseCase.otherVpnRunning.map { _singleEvents.emit( SingleEvent.ToastMessageSingleEvent( @@ -89,7 +87,14 @@ class DashboardViewModel( fun submitAction(action: Action) = viewModelScope.launch { when (action) { - is Action.TogglePrivacyAction -> actionTogglePrivacy() + is Action.ToggleTrackers -> { + getPrivacyStateUseCase.toggleTrackers() + // Add delay here to prevent race condition with trackers state. + delay(200) + fetchStatistics().first() + } + is Action.ToggleLocation -> getPrivacyStateUseCase.toggleLocation() + is Action.ToggleIpScrambling -> actionToggleIpScrambling() is Action.ShowFakeMyLocationAction -> _singleEvents.emit(SingleEvent.NavigateToLocationSingleEvent) is Action.ShowAppsPermissions -> @@ -98,8 +103,6 @@ class DashboardViewModel( _singleEvents.emit(SingleEvent.NavigateToInternetActivityPrivacySingleEvent) is Action.ShowTrackers -> _singleEvents.emit(SingleEvent.NavigateToTrackersSingleEvent) - is Action.CloseQuickPrivacyDisabledMessage -> - getPrivacyStateUseCase.resetQuickPrivacyDisabledMessage() is Action.ShowMostLeakedApp -> actionShowMostLeakedApp() } } @@ -120,9 +123,8 @@ class DashboardViewModel( } } - private suspend fun actionTogglePrivacy() = withContext(Dispatchers.IO) { - val isFirstActivation = getPrivacyStateUseCase.toggleReturnIsFirstActivation() - fetchStatistics().first() + private suspend fun actionToggleIpScrambling() = withContext(Dispatchers.IO) { + val isFirstActivation = getPrivacyStateUseCase.toggleIpScramblingIsFirstActivation() if (isFirstActivation) _singleEvents.emit( SingleEvent.ToastMessageSingleEvent( @@ -152,12 +154,13 @@ class DashboardViewModel( } sealed class Action { - object TogglePrivacyAction : Action() + object ToggleTrackers : Action() + object ToggleLocation : Action() + object ToggleIpScrambling : Action() object ShowFakeMyLocationAction : Action() object ShowInternetActivityPrivacyAction : Action() object ShowAppsPermissions : Action() object ShowTrackers : Action() - object CloseQuickPrivacyDisabledMessage : Action() object ShowMostLeakedApp : Action() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt index 99aa217..afef986 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt @@ -27,13 +27,11 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.snackbar.Snackbar import foundation.e.privacycentralapp.DependencyContainer import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.NavToolbarFragment import foundation.e.privacycentralapp.common.ToggleAppsAdapter -import foundation.e.privacycentralapp.common.initQuickPrivacySnackbar import foundation.e.privacycentralapp.common.setToolTipForAsterisk import foundation.e.privacycentralapp.databinding.FragmentInternetActivityPolicyBinding import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode @@ -53,8 +51,6 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac private var _binding: FragmentInternetActivityPolicyBinding? = null private val binding get() = _binding!! - private var qpDisabledSnackbar: Snackbar? = null - private fun displayToast(message: String) { Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT) .show() @@ -120,10 +116,6 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac } } - qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) { - viewModel.submitAction(InternetPrivacyViewModel.Action.CloseQuickPrivacyDisabledMessage) - } - viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { render(viewModel.state.value) @@ -152,9 +144,6 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac override fun getTitle(): String = getString(R.string.ipscrambling_title) private fun render(state: InternetPrivacyState) { - if (state.showQuickPrivacyDisabledMessage) qpDisabledSnackbar?.show() - else qpDisabledSnackbar?.dismiss() - binding.radioUseHiddenIp.radiobutton.apply { isChecked = state.mode in listOf( InternetPrivacyMode.HIDE_IP, @@ -207,7 +196,6 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac override fun onDestroyView() { super.onDestroyView() - qpDisabledSnackbar = null _binding = null } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyState.kt index 6991196..54b7e01 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyState.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyState.kt @@ -27,7 +27,6 @@ data class InternetPrivacyState( val selectedLocation: String = "", val availableLocationIds: List<String> = emptyList(), val forceRedraw: Boolean = false, - val showQuickPrivacyDisabledMessage: Boolean = false ) { fun getApps(): List<Pair<ApplicationDescription, Boolean>> { return availableApps.map { it to (it.packageName !in bypassTorApps) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt index be6cd4d..bbd6239 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt @@ -73,9 +73,6 @@ class InternetPrivacyViewModel( suspend fun doOnStartedState() = withContext(Dispatchers.IO) { launch { merge( - getQuickPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map { - _state.update { s -> s.copy(showQuickPrivacyDisabledMessage = it) } - }, appListUseCase.getAppsUsingInternet().map { apps -> _state.update { s -> s.copy( @@ -84,17 +81,8 @@ class InternetPrivacyViewModel( ) } }, - if (getQuickPrivacyStateUseCase.isQuickPrivacyEnabled) - ipScramblingStateUseCase.internetPrivacyMode.map { - _state.update { s -> s.copy(mode = it) } - } - else ipScramblingStateUseCase.configuredMode.map { - _state.update { s -> - s.copy( - mode = if (it) InternetPrivacyMode.HIDE_IP - else InternetPrivacyMode.REAL_IP - ) - } + ipScramblingStateUseCase.internetPrivacyMode.map { + _state.update { s -> s.copy(mode = it) } } ).collect {} } @@ -129,8 +117,6 @@ class InternetPrivacyViewModel( is Action.UseHiddenIPAction -> actionUseHiddenIP() is Action.ToggleAppIpScrambled -> actionToggleAppIpScrambled(action) is Action.SelectLocationAction -> actionSelectLocation(action) - is Action.CloseQuickPrivacyDisabledMessage -> - getQuickPrivacyStateUseCase.resetQuickPrivacyDisabledMessage() } } @@ -167,6 +153,5 @@ class InternetPrivacyViewModel( object UseHiddenIPAction : Action() data class ToggleAppIpScrambled(val packageName: String) : Action() data class SelectLocationAction(val position: Int) : Action() - object CloseQuickPrivacyDisabledMessage : Action() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt index 537d0b6..9e3f854 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt @@ -33,7 +33,6 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM @@ -52,7 +51,6 @@ import foundation.e.privacycentralapp.DependencyContainer import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.NavToolbarFragment -import foundation.e.privacycentralapp.common.initQuickPrivacySnackbar import foundation.e.privacycentralapp.databinding.FragmentFakeLocationBinding import foundation.e.privacycentralapp.domain.entities.LocationMode import foundation.e.privacycentralapp.features.location.FakeLocationViewModel.Action @@ -79,8 +77,6 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) private var mapboxMap: MapboxMap? = null private var locationComponent: LocationComponent? = null - private var qpDisabledSnackbar: Snackbar? = null - private var inputJob: Job? = null private val locationPermissionRequest = registerForActivityResult( @@ -147,10 +143,6 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) } } - qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) { - viewModel.submitAction(Action.CloseQuickPrivacyDisabledMessage) - } - viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { render(viewModel.state.value) @@ -266,9 +258,6 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) @SuppressLint("MissingPermission") private fun render(state: FakeLocationState) { - if (state.showQuickPrivacyDisabledMessage) qpDisabledSnackbar?.show() - else qpDisabledSnackbar?.dismiss() - binding.radioUseRandomLocation.isChecked = state.mode == LocationMode.RANDOM_LOCATION binding.radioUseSpecificLocation.isChecked = state.mode == LocationMode.SPECIFIC_LOCATION @@ -379,7 +368,6 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) override fun onDestroyView() { super.onDestroyView() binding.mapView.onDestroy() - qpDisabledSnackbar = null mapboxMap = null locationComponent = null inputJob = null diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationState.kt index 9513f77..50d7a14 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationState.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationState.kt @@ -26,5 +26,4 @@ data class FakeLocationState( val specificLatitude: Float? = null, val specificLongitude: Float? = null, val forceRefresh: Boolean = false, - val showQuickPrivacyDisabledMessage: Boolean = false ) diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt index 8db3537..1cdf9f4 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt @@ -66,9 +66,6 @@ class FakeLocationViewModel( ) } }, - getQuickPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map { - _state.update { s -> s.copy(showQuickPrivacyDisabledMessage = it) } - }, specificLocationInputFlow .debounce(SET_SPECIFIC_LOCATION_DELAY).map { action -> fakeLocationStateUseCase.setSpecificLocation(action.latitude, action.longitude) @@ -96,8 +93,6 @@ class FakeLocationViewModel( is Action.UseRandomLocationAction -> fakeLocationStateUseCase.setRandomLocation() is Action.UseRealLocationAction -> fakeLocationStateUseCase.stopFakeLocation() - is Action.CloseQuickPrivacyDisabledMessage -> - getQuickPrivacyStateUseCase.resetQuickPrivacyDisabledMessage() } } @@ -127,6 +122,5 @@ class FakeLocationViewModel( val latitude: Float, val longitude: Float ) : Action() - object CloseQuickPrivacyDisabledMessage : Action() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt index 491f625..8adf256 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt @@ -28,14 +28,12 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.snackbar.Snackbar import foundation.e.privacycentralapp.DependencyContainer import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.AppsAdapter import foundation.e.privacycentralapp.common.GraphHolder import foundation.e.privacycentralapp.common.NavToolbarFragment -import foundation.e.privacycentralapp.common.initQuickPrivacySnackbar import foundation.e.privacycentralapp.common.setToolTipForAsterisk import foundation.e.privacycentralapp.databinding.FragmentTrackersBinding import foundation.e.privacycentralapp.databinding.TrackersItemGraphBinding @@ -58,7 +56,6 @@ class TrackersFragment : private var dayGraphHolder: GraphHolder? = null private var monthGraphHolder: GraphHolder? = null private var yearGraphHolder: GraphHolder? = null - private var qpDisabledSnackbar: Snackbar? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -79,10 +76,6 @@ class TrackersFragment : } } - qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) { - viewModel.submitAction(TrackersViewModel.Action.CloseQuickPrivacyDisabledMessage) - } - setToolTipForAsterisk( textView = binding.trackersAppsListTitle, textId = R.string.trackers_applist_title, @@ -137,9 +130,6 @@ class TrackersFragment : override fun getTitle() = getString(R.string.trackers_title) private fun render(state: TrackersState) { - if (state.showQuickPrivacyDisabledMessage) qpDisabledSnackbar?.show() - else qpDisabledSnackbar?.dismiss() - state.dayStatistics?.let { renderGraph(it, dayGraphHolder!!, binding.graphDay) } state.monthStatistics?.let { renderGraph(it, monthGraphHolder!!, binding.graphMonth) } state.yearStatistics?.let { renderGraph(it, yearGraphHolder!!, binding.graphYear) } @@ -171,7 +161,6 @@ class TrackersFragment : override fun onDestroyView() { super.onDestroyView() - qpDisabledSnackbar = null dayGraphHolder = null monthGraphHolder = null yearGraphHolder = null diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersState.kt index 2437366..a3bb80a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersState.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersState.kt @@ -25,5 +25,4 @@ data class TrackersState( val monthStatistics: TrackersPeriodicStatistics? = null, val yearStatistics: TrackersPeriodicStatistics? = null, val apps: List<AppWithCounts>? = null, - val showQuickPrivacyDisabledMessage: Boolean = false ) diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt index 3869c39..07828f8 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt @@ -46,9 +46,6 @@ class TrackersViewModel( suspend fun doOnStartedState() = withContext(Dispatchers.IO) { merge( - getQuickPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map { - _state.update { s -> s.copy(showQuickPrivacyDisabledMessage = it) } - }, trackersStatisticsUseCase.listenUpdates().map { trackersStatisticsUseCase.getDayMonthYearStatistics() .let { (day, month, year) -> @@ -70,9 +67,6 @@ class TrackersViewModel( fun submitAction(action: Action) = viewModelScope.launch { when (action) { is Action.ClickAppAction -> actionClickApp(action) - is Action.CloseQuickPrivacyDisabledMessage -> { - getQuickPrivacyStateUseCase.resetQuickPrivacyDisabledMessage() - } } } @@ -89,6 +83,5 @@ class TrackersViewModel( sealed class Action { data class ClickAppAction(val packageName: String) : Action() - object CloseQuickPrivacyDisabledMessage : Action() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt index cd4f6b2..6aeac8e 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt @@ -34,7 +34,6 @@ import foundation.e.privacycentralapp.DependencyContainer import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.common.NavToolbarFragment -import foundation.e.privacycentralapp.common.initQuickPrivacySnackbar import foundation.e.privacycentralapp.databinding.ApptrackersFragmentBinding import kotlinx.coroutines.launch @@ -63,8 +62,6 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { private var _binding: ApptrackersFragmentBinding? = null private val binding get() = _binding!! - private var qpDisabledSnackbar: Snackbar? = null - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (arguments == null || @@ -101,10 +98,6 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { ) } - qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) { - viewModel.submitAction(AppTrackersViewModel.Action.CloseQuickPrivacyDisabledMessage) - } - viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.singleEvents.collect { event -> @@ -117,6 +110,12 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { } catch (e: ActivityNotFoundException) { displayToast("No application to see webpages") } + is AppTrackersViewModel.SingleEvent.ToastTrackersControlDisabled -> + Snackbar.make( + binding.root, + R.string.apptrackers_tracker_control_disabled_message, + Snackbar.LENGTH_LONG + ).show() } } } @@ -137,9 +136,6 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { } private fun render(state: AppTrackersState) { - if (state.showQuickPrivacyDisabledMessage) qpDisabledSnackbar?.show() - else qpDisabledSnackbar?.dismiss() - binding.trackersCountSummary.text = if (state.getTrackersCount() == 0) "" else getString( R.string.apptrackers_trackers_count_summary, @@ -176,7 +172,6 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { override fun onDestroyView() { super.onDestroyView() - qpDisabledSnackbar = null _binding = null } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt index d6d0858..8088443 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt @@ -26,7 +26,7 @@ data class AppTrackersState( val trackersWithWhiteList: List<Pair<Tracker, Boolean>>? = null, val leaked: Int = 0, val blocked: Int = 0, - val isQuickPrivacyEnabled: Boolean = false, + val isTrackersBlockingEnabled: Boolean = false, val showQuickPrivacyDisabledMessage: Boolean = false, ) { fun getTrackersStatus(): List<Pair<Tracker, Boolean>>? { @@ -34,7 +34,7 @@ data class AppTrackersState( } fun getTrackersCount() = trackersWithWhiteList?.size ?: 0 - fun getBlockedTrackersCount(): Int = if (isQuickPrivacyEnabled && isBlockingActivated) + fun getBlockedTrackersCount(): Int = if (isTrackersBlockingEnabled && isBlockingActivated) trackersWithWhiteList?.count { !it.second } ?: 0 else 0 } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt index 52ef2c4..1a33844 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt @@ -21,6 +21,7 @@ import android.net.Uri import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import foundation.e.privacycentralapp.domain.entities.TrackerMode import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase @@ -66,11 +67,8 @@ class AppTrackersViewModel( suspend fun doOnStartedState() = withContext(Dispatchers.IO) { merge( - getQuickPrivacyStateUseCase.quickPrivacyEnabledFlow.map { - _state.update { s -> s.copy(isQuickPrivacyEnabled = it) } - }, - getQuickPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map { - _state.update { s -> s.copy(showQuickPrivacyDisabledMessage = it) } + getQuickPrivacyStateUseCase.trackerMode.map { + _state.update { s -> s.copy(isTrackersBlockingEnabled = it != TrackerMode.VULNERABLE) } }, trackersStatisticsUseCase.listenUpdates().map { fetchStatistics() } ).collect { } @@ -81,13 +79,14 @@ class AppTrackersViewModel( is Action.BlockAllToggleAction -> blockAllToggleAction(action) is Action.ToggleTrackerAction -> toggleTrackerAction(action) is Action.ClickTracker -> actionClickTracker(action) - is Action.CloseQuickPrivacyDisabledMessage -> - getQuickPrivacyStateUseCase.resetQuickPrivacyDisabledMessage() } } private suspend fun blockAllToggleAction(action: Action.BlockAllToggleAction) { withContext(Dispatchers.IO) { + if (!state.value.isTrackersBlockingEnabled) { + _singleEvents.emit(SingleEvent.ToastTrackersControlDisabled) + } trackersStateUseCase.toggleAppWhitelist(appUid, !action.isBlocked) _state.update { it.copy( @@ -99,6 +98,10 @@ class AppTrackersViewModel( private suspend fun toggleTrackerAction(action: Action.ToggleTrackerAction) { withContext(Dispatchers.IO) { + if (!state.value.isTrackersBlockingEnabled) { + _singleEvents.emit(SingleEvent.ToastTrackersControlDisabled) + } + if (state.value.isBlockingActivated) { trackersStateUseCase.blockTracker(appUid, action.tracker, action.isBlocked) _state.update { @@ -141,12 +144,12 @@ class AppTrackersViewModel( sealed class SingleEvent { data class ErrorEvent(@StringRes val errorResId: Int) : SingleEvent() data class OpenUrl(val url: Uri) : SingleEvent() + object ToastTrackersControlDisabled : SingleEvent() } sealed class Action { data class BlockAllToggleAction(val isBlocked: Boolean) : Action() data class ToggleTrackerAction(val tracker: Tracker, val isBlocked: Boolean) : Action() data class ClickTracker(val tracker: Tracker) : Action() - object CloseQuickPrivacyDisabledMessage : Action() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt index 58ac797..a81f5b5 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt @@ -24,7 +24,6 @@ import android.os.Bundle import androidx.fragment.app.FragmentActivity import androidx.fragment.app.add import androidx.fragment.app.commit -import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.features.dashboard.DashboardFragment import foundation.e.privacycentralapp.features.trackers.TrackersFragment @@ -37,11 +36,6 @@ open class MainActivity : FragmentActivity(R.layout.activity_main) { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - - (application as? PrivacyCentralApplication) - ?.dependencyContainer?.getQuickPrivacyStateUseCase - ?.resetQuickPrivacyDisabledMessage() - handleIntent(intent) } 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 ddfcc2e..3abe21b 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt @@ -85,14 +85,14 @@ class Widget : AppWidgetProvider() { getPrivacyStateUseCase.quickPrivacyState, getPrivacyStateUseCase.trackerMode, getPrivacyStateUseCase.isLocationHidden, - getPrivacyStateUseCase.isIpHidden, - ) { quickPrivacyState, trackerMode, isLocationHidden, isIpHidden -> + getPrivacyStateUseCase.ipScramblingMode, + ) { quickPrivacyState, trackerMode, isLocationHidden, ipScramblingMode -> State( quickPrivacyState = quickPrivacyState, trackerMode = trackerMode, isLocationHidden = isLocationHidden, - isIpHidden = isIpHidden + ipScramblingMode = ipScramblingMode ) }.sample(50) .combine( 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 831c06f..4a103e0 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt @@ -26,10 +26,13 @@ import foundation.e.privacycentralapp.R class WidgetCommandReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - val privacyCentralApplication = (context?.applicationContext as? PrivacyCentralApplication) + val getQuickPrivacyStateUseCase = (context?.applicationContext as? PrivacyCentralApplication)?.dependencyContainer?.getQuickPrivacyStateUseCase + when (intent?.action) { - ACTION_TOGGLE_PRIVACY -> { - if (privacyCentralApplication?.dependencyContainer?.getQuickPrivacyStateUseCase?.toggleReturnIsFirstActivation() == true) { + ACTION_TOGGLE_TRACKERS -> getQuickPrivacyStateUseCase?.toggleTrackers() + ACTION_TOGGLE_LOCATION -> getQuickPrivacyStateUseCase?.toggleLocation() + ACTION_TOGGLE_IPSCRAMBLING -> { + if (getQuickPrivacyStateUseCase?.toggleIpScramblingIsFirstActivation() == true) { Toast.makeText( context, context.getString(R.string.dashboard_first_ipscrambling_activation), @@ -42,6 +45,8 @@ class WidgetCommandReceiver : BroadcastReceiver() { } companion object { - const val ACTION_TOGGLE_PRIVACY = "toggle_privacy" + const val ACTION_TOGGLE_TRACKERS = "toggle_trackers" + const val ACTION_TOGGLE_LOCATION = "toggle_location" + const val ACTION_TOGGLE_IPSCRAMBLING = "toggle_ipscrambling" } } 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 682e5cc..4fdbb03 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt @@ -30,16 +30,19 @@ import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.Widget import foundation.e.privacycentralapp.Widget.Companion.isDarkText import foundation.e.privacycentralapp.common.extensions.dpToPxF +import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState import foundation.e.privacycentralapp.domain.entities.TrackerMode import foundation.e.privacycentralapp.main.MainActivity -import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_PRIVACY +import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_IPSCRAMBLING +import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_LOCATION +import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_TRACKERS data class State( val quickPrivacyState: QuickPrivacyState = QuickPrivacyState.DISABLED, val trackerMode: TrackerMode = TrackerMode.VULNERABLE, val isLocationHidden: Boolean = false, - val isIpHidden: Boolean? = false, + val ipScramblingMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP_LOADING, val dayStatistics: List<Pair<Int, Int>> = emptyList(), val activeTrackersCount: Int = 0, ) @@ -71,19 +74,21 @@ fun render( } ) ) + setImageViewResource( - R.id.toggle_privacy_central, - if (state.quickPrivacyState.isEnabled()) R.drawable.ic_switch_enabled - else R.drawable.ic_switch_disabled + R.id.toggle_trackers, + if (state.trackerMode == TrackerMode.VULNERABLE) + R.drawable.ic_switch_disabled + else R.drawable.ic_switch_enabled ) setOnClickPendingIntent( - R.id.toggle_privacy_central, + R.id.toggle_trackers, PendingIntent.getBroadcast( context, - REQUEST_CODE_TOGGLE, + REQUEST_CODE_TOGGLE_TRACKERS, Intent(context, WidgetCommandReceiver::class.java).apply { - action = ACTION_TOGGLE_PRIVACY + action = ACTION_TOGGLE_TRACKERS }, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT ) @@ -100,6 +105,24 @@ fun render( ) ) + setImageViewResource( + R.id.toggle_location, + if (state.isLocationHidden) R.drawable.ic_switch_enabled + else R.drawable.ic_switch_disabled + ) + + setOnClickPendingIntent( + R.id.toggle_location, + PendingIntent.getBroadcast( + context, + REQUEST_CODE_TOGGLE_LOCATION, + Intent(context, WidgetCommandReceiver::class.java).apply { + action = ACTION_TOGGLE_LOCATION + }, + FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT + ) + ) + setTextViewText( R.id.state_geolocation, context.getString( @@ -108,15 +131,33 @@ fun render( ) ) + setImageViewResource( + R.id.toggle_ipscrambling, + if (state.ipScramblingMode.isChecked) R.drawable.ic_switch_enabled + else R.drawable.ic_switch_disabled + ) + + setOnClickPendingIntent( + R.id.toggle_ipscrambling, + PendingIntent.getBroadcast( + context, + REQUEST_CODE_TOGGLE_IPSCRAMBLING, + Intent(context, WidgetCommandReceiver::class.java).apply { + action = ACTION_TOGGLE_IPSCRAMBLING + }, + FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT + ) + ) + setTextViewText( R.id.state_ip_address, context.getString( - if (state.isIpHidden == true) R.string.widget_state_ipaddress_on + if (state.ipScramblingMode == InternetPrivacyMode.HIDE_IP) R.string.widget_state_ipaddress_on else R.string.widget_state_ipaddress_off ) ) - val loading = state.isIpHidden == null + val loading = state.ipScramblingMode.isLoading setViewVisibility(R.id.state_ip_address, if (loading) View.GONE else View.VISIBLE) @@ -265,8 +306,10 @@ private val leakedBarIds = listOf( ) private const val REQUEST_CODE_DASHBOARD = 1 -private const val REQUEST_CODE_TOGGLE = 2 private const val REQUEST_CODE_TRACKERS = 3 +private const val REQUEST_CODE_TOGGLE_TRACKERS = 4 +private const val REQUEST_CODE_TOGGLE_LOCATION = 5 +private const val REQUEST_CODE_TOGGLE_IPSCRAMBLING = 6 private const val REQUEST_CODE_HIGHLIGHT = 100 fun applyDarkText(context: Context, state: State, views: RemoteViews) { |