aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/foundation/e/privacycentralapp/features
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/foundation/e/privacycentralapp/features')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt16
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt21
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt50
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt11
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt85
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt65
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt18
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt10
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt10
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt27
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt19
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt6
12 files changed, 200 insertions, 138 deletions
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 7270c32..3ed3168 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
@@ -57,7 +57,8 @@ class DashboardFeature(
val trackersCount: Int? = null,
val allowedTrackersCount: Int? = null,
val dayStatistics: List<Pair<Int, Int>>? = null,
- val dayLabels: List<String>? = null
+ val dayLabels: List<String>? = null,
+ val showQuickPrivacyDisabledMessage: Boolean = false
)
sealed class SingleEvent {
@@ -77,6 +78,7 @@ class DashboardFeature(
object ShowAppsPermissions : Action()
object ShowTrackers : Action()
object FetchStatistics : Action()
+ object CloseQuickPrivacyDisabledMessage : Action()
}
sealed class Effect {
@@ -99,6 +101,7 @@ class DashboardFeature(
object NewStatisticsAvailablesEffect : Effect()
object FirstIPTrackerActivationEffect : Effect()
data class LocationHiddenUpdatedEffect(val isLocationHidden: Boolean) : Effect()
+ data class ShowQuickPrivacyDisabledMessageEffect(val show: Boolean) : Effect()
}
companion object {
@@ -129,7 +132,7 @@ class DashboardFeature(
isLocationHidden = effect.isLocationHidden
)
is Effect.UpdateLocationModeEffect -> state.copy(locationMode = effect.mode)
-
+ is Effect.ShowQuickPrivacyDisabledMessageEffect -> state.copy(showQuickPrivacyDisabledMessage = effect.show)
else -> state
}
},
@@ -161,7 +164,10 @@ class DashboardFeature(
},
getPrivacyStateUseCase.locationMode.map {
Effect.UpdateLocationModeEffect(it)
- }
+ },
+ getPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map {
+ Effect.ShowQuickPrivacyDisabledMessageEffect(it)
+ },
)
Action.ShowFakeMyLocationAction -> flowOf(Effect.OpenFakeMyLocationEffect)
Action.ShowAppsPermissions -> flowOf(Effect.OpenAppsPermissionsEffect)
@@ -183,6 +189,10 @@ class DashboardFeature(
)
}
}
+ is Action.CloseQuickPrivacyDisabledMessage -> {
+ getPrivacyStateUseCase.resetQuickPrivacyDisabledMessage()
+ flowOf(Effect.NoEffect)
+ }
}
},
singleEventProducer = { _, _, effect ->
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 ea470a2..32549c9 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
@@ -26,15 +26,17 @@ import android.widget.Toast
import androidx.core.content.ContextCompat.getColor
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
-import androidx.fragment.app.add
import androidx.fragment.app.commit
+import androidx.fragment.app.replace
import androidx.lifecycle.lifecycleScope
+import com.google.android.material.snackbar.Snackbar
import foundation.e.flowmvi.MVIView
import foundation.e.privacycentralapp.DependencyContainer
import foundation.e.privacycentralapp.PrivacyCentralApplication
import foundation.e.privacycentralapp.R
import foundation.e.privacycentralapp.common.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.LocationMode
import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState
@@ -65,6 +67,8 @@ class DashboardFragment :
private lateinit var graphHolder: GraphHolder
private lateinit var binding: FragmentDashboardBinding
+ private var qpDisabledSnackbar: Snackbar? = null
+
private var updateUIJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
@@ -79,14 +83,14 @@ class DashboardFragment :
when (event) {
is DashboardFeature.SingleEvent.NavigateToLocationSingleEvent -> {
requireActivity().supportFragmentManager.commit {
- add<FakeLocationFragment>(R.id.container)
+ replace<FakeLocationFragment>(R.id.container)
setReorderingAllowed(true)
addToBackStack("dashboard")
}
}
is DashboardFeature.SingleEvent.NavigateToInternetActivityPrivacySingleEvent -> {
requireActivity().supportFragmentManager.commit {
- add<InternetPrivacyFragment>(R.id.container)
+ replace<InternetPrivacyFragment>(R.id.container)
setReorderingAllowed(true)
addToBackStack("dashboard")
}
@@ -97,7 +101,7 @@ class DashboardFragment :
}
DashboardFeature.SingleEvent.NavigateToTrackersSingleEvent -> {
requireActivity().supportFragmentManager.commit {
- add<TrackersFragment>(R.id.container)
+ replace<TrackersFragment>(R.id.container)
setReorderingAllowed(true)
addToBackStack("dashboard")
}
@@ -138,6 +142,10 @@ class DashboardFragment :
binding.amITracked.container.setOnClickListener {
viewModel.submitAction(DashboardFeature.Action.ShowTrackers)
}
+
+ qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) {
+ viewModel.submitAction(DashboardFeature.Action.CloseQuickPrivacyDisabledMessage)
+ }
}
override fun onResume() {
@@ -149,6 +157,8 @@ class DashboardFragment :
}
}
+ render(viewModel.dashboardFeature.state.value)
+
viewModel.submitAction(DashboardFeature.Action.FetchStatistics)
}
@@ -162,6 +172,9 @@ class DashboardFragment :
}
override fun render(state: State) {
+ if (state.showQuickPrivacyDisabledMessage) qpDisabledSnackbar?.show()
+ else qpDisabledSnackbar?.dismiss()
+
binding.stateLabel.text = getString(
when (state.quickPrivacyState) {
QuickPrivacyState.DISABLED -> R.string.dashboard_state_title_off
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt
index eca1578..8e4318d 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt
@@ -36,6 +36,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -63,7 +64,8 @@ class InternetPrivacyFeature(
val bypassTorApps: Collection<String>,
val selectedLocation: String,
val availableLocationIds: List<String>,
- val forceRedraw: Boolean = false
+ val forceRedraw: Boolean = false,
+ val showQuickPrivacyDisabledMessage: Boolean = false
) {
fun getApps(): List<Pair<ApplicationDescription, Boolean>> {
return availableApps.map { it to (it.packageName !in bypassTorApps) }
@@ -84,6 +86,7 @@ class InternetPrivacyFeature(
data class AndroidVpnActivityResultAction(val resultCode: Int) : Action()
data class ToggleAppIpScrambled(val packageName: String) : Action()
data class SelectLocationAction(val position: Int) : Action()
+ object CloseQuickPrivacyDisabledMessage : Action()
}
sealed class Effect {
@@ -100,6 +103,7 @@ class InternetPrivacyFeature(
data class LocationSelectedEffect(val locationId: String) : Effect()
object WarningStartingLongEffect : Effect()
data class ErrorEffect(val message: String) : Effect()
+ data class ShowQuickPrivacyDisabledMessageEffect(val show: Boolean) : Effect()
}
companion object {
@@ -131,6 +135,7 @@ class InternetPrivacyFeature(
)
is Effect.LocationSelectedEffect -> state.copy(selectedLocation = effect.locationId)
Effect.QuickPrivacyDisabledWarningEffect -> state.copy(forceRedraw = !state.forceRedraw)
+ is Effect.ShowQuickPrivacyDisabledMessageEffect -> state.copy(showQuickPrivacyDisabledMessage = effect.show)
else -> state
}
},
@@ -139,9 +144,24 @@ class InternetPrivacyFeature(
action is Action.LoadInternetModeAction -> merge(
getQuickPrivacyStateUseCase.quickPrivacyEnabledFlow
.map { Effect.QuickPrivacyUpdatedEffect(it) },
- ipScramblingStateUseCase.internetPrivacyMode
- .map { Effect.ModeUpdatedEffect(it) }
- .shareIn(scope = coroutineScope, started = SharingStarted.Lazily, replay = 0),
+ getQuickPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map {
+ Effect.ShowQuickPrivacyDisabledMessageEffect(it)
+ },
+ getQuickPrivacyStateUseCase.quickPrivacyEnabledFlow.flatMapLatest { enabled ->
+ if (enabled) ipScramblingStateUseCase.internetPrivacyMode
+ .map { Effect.ModeUpdatedEffect(it) }
+ .shareIn(
+ scope = coroutineScope,
+ started = SharingStarted.Lazily,
+ replay = 0
+ )
+ else ipScramblingStateUseCase.configuredMode.map {
+ Effect.ModeUpdatedEffect(
+ if (it) InternetPrivacyMode.HIDE_IP
+ else InternetPrivacyMode.REAL_IP
+ )
+ }
+ },
appListUseCase.getAppsUsingInternet().map { apps ->
Effect.AvailableAppsListEffect(
apps,
@@ -175,24 +195,16 @@ class InternetPrivacyFeature(
InternetPrivacyMode.HIDE_IP_LOADING,
InternetPrivacyMode.REAL_IP_LOADING
) -> {
- if (getQuickPrivacyStateUseCase.isQuickPrivacyEnabled) {
- ipScramblingStateUseCase.toggle(hideIp = false)
- flowOf(Effect.ModeUpdatedEffect(InternetPrivacyMode.REAL_IP_LOADING))
- } else {
- flowOf(Effect.QuickPrivacyDisabledWarningEffect)
- }
+ ipScramblingStateUseCase.toggle(hideIp = false)
+ flowOf(Effect.ModeUpdatedEffect(InternetPrivacyMode.REAL_IP_LOADING))
}
action is Action.UseHiddenIPAction
&& state.mode in listOf(
InternetPrivacyMode.REAL_IP,
InternetPrivacyMode.REAL_IP_LOADING
) -> {
- if (getQuickPrivacyStateUseCase.isQuickPrivacyEnabled) {
- ipScramblingStateUseCase.toggle(hideIp = true)
- flowOf(Effect.ModeUpdatedEffect(InternetPrivacyMode.HIDE_IP_LOADING))
- } else {
- flowOf(Effect.QuickPrivacyDisabledWarningEffect)
- }
+ ipScramblingStateUseCase.toggle(hideIp = true)
+ flowOf(Effect.ModeUpdatedEffect(InternetPrivacyMode.HIDE_IP_LOADING))
}
action is Action.ToggleAppIpScrambled -> {
@@ -208,6 +220,10 @@ class InternetPrivacyFeature(
flowOf(Effect.NoEffect)
}
}
+ action is Action.CloseQuickPrivacyDisabledMessage -> {
+ getQuickPrivacyStateUseCase.resetQuickPrivacyDisabledMessage()
+ flowOf(Effect.NoEffect)
+ }
else -> flowOf(Effect.NoEffect)
}
},
@@ -216,8 +232,6 @@ class InternetPrivacyFeature(
effect is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message)
effect is Effect.WarningStartingLongEffect ->
SingleEvent.ErrorEvent(R.string.ipscrambling_warning_starting_long)
- effect is Effect.QuickPrivacyDisabledWarningEffect -> SingleEvent.ErrorEvent(error = R.string.ipscrambling_error_quickprivacy_disabled)
-
action is Action.UseHiddenIPAction
&& effect is Effect.ShowAndroidVpnDisclaimerEffect ->
SingleEvent.StartAndroidVpnActivityEvent(effect.intent)
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 f49399f..2452d33 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
@@ -26,12 +26,14 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.snackbar.Snackbar
import foundation.e.flowmvi.MVIView
import foundation.e.privacycentralapp.DependencyContainer
import foundation.e.privacycentralapp.PrivacyCentralApplication
import foundation.e.privacycentralapp.R
import foundation.e.privacycentralapp.common.NavToolbarFragment
import foundation.e.privacycentralapp.common.ToggleAppsAdapter
+import foundation.e.privacycentralapp.common.initQuickPrivacySnackbar
import foundation.e.privacycentralapp.databinding.FragmentInternetActivityPolicyBinding
import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
import foundation.e.privacycentralapp.extensions.toText
@@ -56,6 +58,8 @@ class InternetPrivacyFragment :
private lateinit var binding: FragmentInternetActivityPolicyBinding
+ private var qpDisabledSnackbar: Snackbar? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launchWhenStarted {
@@ -132,12 +136,19 @@ class InternetPrivacyFragment :
}
}
+ qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) {
+ viewModel.submitAction(InternetPrivacyFeature.Action.CloseQuickPrivacyDisabledMessage)
+ }
+
binding.executePendingBindings()
}
override fun getTitle(): String = getString(R.string.ipscrambling_title)
override fun render(state: InternetPrivacyFeature.State) {
+ if (state.showQuickPrivacyDisabledMessage) qpDisabledSnackbar?.show()
+ else qpDisabledSnackbar?.dismiss()
+
binding.radioUseHiddenIp.radiobutton.apply {
isChecked = state.mode in listOf(
InternetPrivacyMode.HIDE_IP,
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt
index a7869ce..85a507d 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt
@@ -27,7 +27,6 @@ import foundation.e.privacycentralapp.domain.entities.LocationMode
import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase
import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -48,16 +47,16 @@ class FakeLocationFeature(
singleEventProducer
) {
data class State(
- val isEnabled: Boolean,
- val mode: LocationMode,
- val currentLocation: Location?,
+ val mode: LocationMode = LocationMode.REAL_LOCATION,
+ val currentLocation: Location? = null,
val specificLatitude: Float? = null,
val specificLongitude: Float? = null,
- val forceRefresh: Boolean = false
+ val forceRefresh: Boolean = false,
+ val showQuickPrivacyDisabledMessage: Boolean = false
)
sealed class SingleEvent {
- data class LocationUpdatedEvent(val location: Location?) : SingleEvent()
+ data class LocationUpdatedEvent(val mode: LocationMode, val location: Location?) : SingleEvent()
data class ErrorEvent(val error: String) : SingleEvent()
}
@@ -70,10 +69,10 @@ class FakeLocationFeature(
val latitude: Float,
val longitude: Float
) : Action()
+ object CloseQuickPrivacyDisabledMessage : Action()
}
sealed class Effect {
- data class QuickPrivacyUpdatedEffect(val isEnabled: Boolean) : Effect()
data class LocationModeUpdatedEffect(
val mode: LocationMode,
val latitude: Float? = null,
@@ -81,17 +80,13 @@ class FakeLocationFeature(
) : Effect()
data class LocationUpdatedEffect(val location: Location?) : Effect()
data class ErrorEffect(val message: String) : Effect()
- object QuickPrivacyDisabledWarningEffect : Effect()
object NoEffect : Effect()
+ data class ShowQuickPrivacyDisabledMessageEffect(val show: Boolean) : Effect()
}
companion object {
fun create(
- initialState: State = State(
- isEnabled = false,
- mode = LocationMode.REAL_LOCATION,
- currentLocation = null
- ),
+ initialState: State = State(),
getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
fakeLocationStateUseCase: FakeLocationStateUseCase,
coroutineScope: CoroutineScope
@@ -99,66 +94,56 @@ class FakeLocationFeature(
initialState, coroutineScope,
reducer = { state, effect ->
when (effect) {
- is Effect.QuickPrivacyUpdatedEffect -> state.copy(isEnabled = effect.isEnabled)
is Effect.LocationModeUpdatedEffect -> state.copy(
mode = effect.mode,
specificLatitude = effect.latitude,
specificLongitude = effect.longitude
)
- Effect.QuickPrivacyDisabledWarningEffect -> state.copy(forceRefresh = !state.forceRefresh)
+ is Effect.ShowQuickPrivacyDisabledMessageEffect -> state.copy(showQuickPrivacyDisabledMessage = effect.show)
else -> state
}
},
- actor = { state, action ->
+ actor = { _, action ->
when (action) {
- is Action.Init -> merge(
- getQuickPrivacyStateUseCase.quickPrivacyEnabledFlow.map { Effect.QuickPrivacyUpdatedEffect(it) },
- flow {
- fakeLocationStateUseCase.startListeningLocation()
- val (mode, lat, lon) = fakeLocationStateUseCase.getLocationMode()
- emit(Effect.LocationModeUpdatedEffect(mode = mode, latitude = lat, longitude = lon))
- },
- fakeLocationStateUseCase.currentLocation.map { Effect.LocationUpdatedEffect(it) }
- )
+ is Action.Init -> {
+ fakeLocationStateUseCase.startListeningLocation()
+ merge(
+ fakeLocationStateUseCase.configuredLocationMode.map { (mode, lat, lon) ->
+ Effect.LocationModeUpdatedEffect(mode = mode, latitude = lat, longitude = lon)
+ },
+ fakeLocationStateUseCase.currentLocation.map { Effect.LocationUpdatedEffect(it) },
+ getQuickPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map { Effect.ShowQuickPrivacyDisabledMessageEffect(it) },
+ )
+ }
is Action.LeaveScreen -> {
fakeLocationStateUseCase.stopListeningLocation()
flowOf(Effect.NoEffect)
}
is Action.SetSpecificLocationAction -> {
- if (state.isEnabled) {
- fakeLocationStateUseCase.setSpecificLocation(
- action.latitude,
- action.longitude
- )
- flowOf(
- Effect.LocationModeUpdatedEffect(
- mode = LocationMode.SPECIFIC_LOCATION,
- latitude = action.latitude,
- longitude = action.longitude
- )
- )
- } else flowOf(Effect.QuickPrivacyDisabledWarningEffect)
+ fakeLocationStateUseCase.setSpecificLocation(
+ action.latitude,
+ action.longitude
+ )
+ flowOf(Effect.NoEffect)
}
is Action.UseRandomLocationAction -> {
- if (state.isEnabled) {
- fakeLocationStateUseCase.setRandomLocation()
- flowOf(Effect.LocationModeUpdatedEffect(LocationMode.RANDOM_LOCATION))
- } else flowOf(Effect.QuickPrivacyDisabledWarningEffect)
+ fakeLocationStateUseCase.setRandomLocation()
+ flowOf(Effect.NoEffect)
}
is Action.UseRealLocationAction -> {
- if (state.isEnabled) {
- fakeLocationStateUseCase.stopFakeLocation()
- flowOf(Effect.LocationModeUpdatedEffect(LocationMode.REAL_LOCATION))
- } else flowOf(Effect.QuickPrivacyDisabledWarningEffect)
+ fakeLocationStateUseCase.stopFakeLocation()
+ flowOf(Effect.NoEffect)
+ }
+ is Action.CloseQuickPrivacyDisabledMessage -> {
+ getQuickPrivacyStateUseCase.resetQuickPrivacyDisabledMessage()
+ flowOf(Effect.NoEffect)
}
}
},
- singleEventProducer = { _, _, effect ->
+ singleEventProducer = { state, _, effect ->
when (effect) {
is Effect.LocationUpdatedEffect ->
- SingleEvent.LocationUpdatedEvent(effect.location)
- Effect.QuickPrivacyDisabledWarningEffect ->
- SingleEvent.ErrorEvent("Enabled Quick Privacy to use functionalities")
+ SingleEvent.LocationUpdatedEvent(state.mode, effect.location)
is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message)
else -> null
}
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 bc35521..fa26dd0 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
@@ -29,6 +29,7 @@ import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
+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
@@ -48,6 +49,7 @@ 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.extensions.viewModelProviderFactoryOf
@@ -78,6 +80,8 @@ class FakeLocationFragment :
private var mapboxMap: MapboxMap? = null
private var locationComponent: LocationComponent? = null
+ private var qpDisabledSnackbar: Snackbar? = null
+
private var inputJob: Job? = null
companion object {
@@ -97,18 +101,7 @@ class FakeLocationFragment :
displayToast(event.error)
}
is FakeLocationFeature.SingleEvent.LocationUpdatedEvent -> {
- if (isFirstLaunch && mapboxMap != null) {
- mapboxMap?.moveCamera(
- CameraUpdateFactory.newLatLng(
- LatLng(
- event.location?.latitude ?: 0.0,
- event.location?.longitude ?: 0.0
- )
- )
- )
- isFirstLaunch = false
- }
- updateLocation(event.location)
+ updateLocation(event.location, event.mode)
}
}
}
@@ -151,8 +144,14 @@ class FakeLocationFragment :
}
// Bind click listeners once map is ready.
bindClickListeners()
+
+ render(viewModel.fakeLocationFeature.state.value)
}
}
+
+ qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) {
+ viewModel.submitAction(Action.CloseQuickPrivacyDisabledMessage)
+ }
}
private fun getCoordinatesAfterTextChanged(
@@ -232,28 +231,22 @@ class FakeLocationFragment :
@SuppressLint("MissingPermission")
override fun render(state: FakeLocationFeature.State) {
- binding.radioUseRandomLocation.apply {
- isChecked = state.mode == LocationMode.RANDOM_LOCATION
- isEnabled = state.isEnabled
- }
+ if (state.showQuickPrivacyDisabledMessage) qpDisabledSnackbar?.show()
+ else qpDisabledSnackbar?.dismiss()
- binding.radioUseSpecificLocation.apply {
- isChecked = state.mode == LocationMode.SPECIFIC_LOCATION
- isEnabled = state.isEnabled
- }
+ binding.radioUseRandomLocation.isChecked = state.mode == LocationMode.RANDOM_LOCATION
- binding.radioUseRealLocation.apply {
- isChecked = state.mode == LocationMode.REAL_LOCATION
- isEnabled = state.isEnabled
- }
+ binding.radioUseSpecificLocation.isChecked = state.mode == LocationMode.SPECIFIC_LOCATION
+
+ binding.radioUseRealLocation.isChecked = state.mode == LocationMode.REAL_LOCATION
binding.mapView.isEnabled = (state.mode == LocationMode.SPECIFIC_LOCATION)
- if (state.mode != LocationMode.SPECIFIC_LOCATION) {
+ if (state.mode == LocationMode.REAL_LOCATION) {
binding.centeredMarker.isVisible = false
} else {
binding.mapLoader.isVisible = false
- binding.mapOverlay.isVisible = false
+ binding.mapOverlay.isVisible = state.mode != LocationMode.SPECIFIC_LOCATION
binding.centeredMarker.isVisible = true
mapboxMap?.moveCamera(
@@ -276,7 +269,7 @@ class FakeLocationFragment :
override fun actions(): Flow<Action> = viewModel.actions
@SuppressLint("MissingPermission")
- private fun updateLocation(lastLocation: Location?) {
+ private fun updateLocation(lastLocation: Location?, mode: LocationMode) {
lastLocation?.let { location ->
locationComponent?.isLocationComponentEnabled = true
val locationUpdate = LocationUpdate.Builder()
@@ -285,18 +278,24 @@ class FakeLocationFragment :
.build()
locationComponent?.forceLocationUpdate(locationUpdate)
- if (!binding.mapView.isEnabled) {
+ if (mode == LocationMode.REAL_LOCATION) {
binding.mapLoader.isVisible = false
binding.mapOverlay.isVisible = false
- mapboxMap?.animateCamera(
- CameraUpdateFactory.newLatLng(
- LatLng(location.latitude, location.longitude)
- )
+
+ val update = CameraUpdateFactory.newLatLng(
+ LatLng(location.latitude, location.longitude)
)
+
+ if (isFirstLaunch) {
+ mapboxMap?.moveCamera(update)
+ isFirstLaunch = false
+ } else {
+ mapboxMap?.animateCamera(update)
+ }
}
} ?: run {
locationComponent?.isLocationComponentEnabled = false
- if (!binding.mapView.isEnabled) {
+ if (mode == LocationMode.REAL_LOCATION) {
binding.mapLoader.isVisible = true
binding.mapOverlay.isVisible = true
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt
index a606e49..34ddfbe 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt
@@ -24,6 +24,7 @@ import foundation.e.flowmvi.SingleEventProducer
import foundation.e.flowmvi.feature.BaseFeature
import foundation.e.privacycentralapp.domain.entities.AppWithCounts
import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics
+import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.flow
@@ -51,6 +52,7 @@ class TrackersFeature(
val monthStatistics: TrackersPeriodicStatistics? = null,
val yearStatistics: TrackersPeriodicStatistics? = null,
val apps: List<AppWithCounts>? = null,
+ val showQuickPrivacyDisabledMessage: Boolean = false
)
sealed class SingleEvent {
@@ -63,9 +65,11 @@ class TrackersFeature(
object InitAction : Action()
data class ClickAppAction(val packageName: String) : Action()
object FetchStatistics : Action()
+ object CloseQuickPrivacyDisabledMessage : Action()
}
sealed class Effect {
+ object NoEffect : Effect()
data class TrackersStatisticsLoadedEffect(
val dayStatistics: TrackersPeriodicStatistics? = null,
val monthStatistics: TrackersPeriodicStatistics? = null,
@@ -75,14 +79,15 @@ class TrackersFeature(
val apps: List<AppWithCounts>
) : Effect()
data class OpenAppDetailsEffect(val appDesc: AppWithCounts) : Effect()
- object QuickPrivacyDisabledWarningEffect : Effect()
data class ErrorEffect(val message: String) : Effect()
object NewStatisticsAvailablesEffect : Effect()
+ data class ShowQuickPrivacyDisabledMessageEffect(val show: Boolean) : Effect()
}
companion object {
fun create(
initialState: State = State(),
+ getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
coroutineScope: CoroutineScope,
trackersStatisticsUseCase: TrackersStatisticsUseCase
) = TrackersFeature(
@@ -97,6 +102,7 @@ class TrackersFeature(
is Effect.AvailableAppsListEffect -> state.copy(apps = effect.apps)
is Effect.ErrorEffect -> state
+ is Effect.ShowQuickPrivacyDisabledMessageEffect -> state.copy(showQuickPrivacyDisabledMessage = effect.show)
else -> state
}
},
@@ -106,7 +112,10 @@ class TrackersFeature(
flowOf(Effect.NewStatisticsAvailablesEffect),
trackersStatisticsUseCase.listenUpdates().map {
Effect.NewStatisticsAvailablesEffect
- }
+ },
+ getQuickPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map {
+ Effect.ShowQuickPrivacyDisabledMessageEffect(it)
+ },
)
is Action.ClickAppAction -> flowOf(
@@ -131,13 +140,16 @@ class TrackersFeature(
Effect.AvailableAppsListEffect(it)
}
)
+ is Action.CloseQuickPrivacyDisabledMessage -> {
+ getQuickPrivacyStateUseCase.resetQuickPrivacyDisabledMessage()
+ flowOf(Effect.NoEffect)
+ }
}
},
singleEventProducer = { _, _, effect ->
when (effect) {
is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message)
is Effect.OpenAppDetailsEffect -> SingleEvent.OpenAppDetailsEvent(effect.appDesc)
- is Effect.QuickPrivacyDisabledWarningEffect -> SingleEvent.ErrorEvent("Enabled Quick Privacy to use functionalities")
is Effect.NewStatisticsAvailablesEffect -> SingleEvent.NewStatisticsAvailableSingleEvent
else -> null
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt
index 893f4ba..21a90bc 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
@@ -26,6 +26,7 @@ import androidx.fragment.app.replace
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.snackbar.Snackbar
import foundation.e.flowmvi.MVIView
import foundation.e.privacycentralapp.DependencyContainer
import foundation.e.privacycentralapp.PrivacyCentralApplication
@@ -33,6 +34,7 @@ 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.databinding.FragmentTrackersBinding
import foundation.e.privacycentralapp.databinding.TrackersItemGraphBinding
import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics
@@ -57,6 +59,7 @@ class TrackersFragment :
private lateinit var dayGraphHolder: GraphHolder
private lateinit var monthGraphHolder: GraphHolder
private lateinit var yearGraphHolder: GraphHolder
+ private var qpDisabledSnackbar: Snackbar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -111,6 +114,10 @@ class TrackersFragment :
)
}
}
+
+ qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) {
+ viewModel.submitAction(TrackersFeature.Action.CloseQuickPrivacyDisabledMessage)
+ }
}
override fun onResume() {
@@ -121,6 +128,9 @@ class TrackersFragment :
override fun getTitle() = getString(R.string.trackers_title)
override fun render(state: TrackersFeature.State) {
+ 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) }
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 c2a1822..4140381 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
@@ -20,12 +20,14 @@ package foundation.e.privacycentralapp.features.trackers
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import foundation.e.privacycentralapp.common.Factory
+import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
class TrackersViewModel(
+ private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
private val trackersStatisticsUseCase: TrackersStatisticsUseCase
) : ViewModel() {
@@ -35,7 +37,7 @@ class TrackersViewModel(
val trackersFeature: TrackersFeature by lazy {
TrackersFeature.create(
coroutineScope = viewModelScope,
-
+ getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase,
trackersStatisticsUseCase = trackersStatisticsUseCase
)
}
@@ -48,10 +50,14 @@ class TrackersViewModel(
}
class TrackersViewModelFactory(
+ private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
private val trackersStatisticsUseCase: TrackersStatisticsUseCase
) :
Factory<TrackersViewModel> {
override fun create(): TrackersViewModel {
- return TrackersViewModel(trackersStatisticsUseCase)
+ return TrackersViewModel(
+ getQuickPrivacyStateUseCase,
+ trackersStatisticsUseCase
+ )
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt
index c1eef47..ad82337 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt
@@ -55,7 +55,8 @@ class AppTrackersFeature(
val isBlockingActivated: Boolean = false,
val trackers: List<Tracker>? = null,
val whitelist: List<String>? = null,
- val isQuickPrivacyEnabled: Boolean = false
+ val isQuickPrivacyEnabled: Boolean = false,
+ val showQuickPrivacyDisabledMessage: Boolean = false
) {
fun getTrackersStatus(): List<Pair<Tracker, Boolean>>? {
if (trackers != null && whitelist != null) {
@@ -83,6 +84,7 @@ class AppTrackersFeature(
data class ToggleTrackerAction(val tracker: Tracker, val isBlocked: Boolean) : Action()
data class ClickTracker(val tracker: Tracker) : Action()
object FetchStatistics : Action()
+ object CloseQuickPrivacyDisabledMessage : Action()
}
sealed class Effect {
@@ -94,8 +96,8 @@ class AppTrackersFeature(
data class TrackersWhitelistUpdateEffect(val whitelist: List<String>) : Effect()
object NewStatisticsAvailablesEffect : Effect()
data class QuickPrivacyUpdatedEffect(val enabled: Boolean) : Effect()
- object QuickPrivacyDisabledWarningEffect : Effect()
data class OpenUrlEffect(val url: Uri) : Effect()
+ data class ShowQuickPrivacyDisabledMessageEffect(val show: Boolean) : Effect()
}
companion object {
@@ -121,6 +123,7 @@ class AppTrackersFeature(
state.copy(whitelist = effect.whitelist)
is Effect.QuickPrivacyUpdatedEffect ->
state.copy(isQuickPrivacyEnabled = effect.enabled)
+ is Effect.ShowQuickPrivacyDisabledMessageEffect -> state.copy(showQuickPrivacyDisabledMessage = effect.show)
is Effect.ErrorEffect -> state
else -> state
}
@@ -155,14 +158,15 @@ class AppTrackersFeature(
},
getQuickPrivacyStateUseCase.quickPrivacyEnabledFlow.map {
Effect.QuickPrivacyUpdatedEffect(it)
- }
+ },
+ getQuickPrivacyStateUseCase.showQuickPrivacyDisabledMessage.map {
+ Effect.ShowQuickPrivacyDisabledMessageEffect(it)
+ },
)
} ?: flowOf(Effect.ErrorEffect(R.string.apptrackers_error_no_app))
is Action.BlockAllToggleAction ->
- if (!state.isQuickPrivacyEnabled) {
- flowOf(Effect.QuickPrivacyDisabledWarningEffect)
- } else state.appDesc?.uid?.let { appUid ->
+ state.appDesc?.uid?.let { appUid ->
flow {
trackersStateUseCase.toggleAppWhitelist(appUid, !action.isBlocked)
@@ -174,9 +178,7 @@ class AppTrackersFeature(
}
} ?: run { flowOf(Effect.ErrorEffect("No appDesc.")) }
is Action.ToggleTrackerAction -> {
- if (!state.isQuickPrivacyEnabled) {
- flowOf(Effect.QuickPrivacyDisabledWarningEffect)
- } else if (state.isBlockingActivated) {
+ if (state.isBlockingActivated) {
state.appDesc?.uid?.let { appUid ->
flow {
trackersStateUseCase.blockTracker(
@@ -210,15 +212,16 @@ class AppTrackersFeature(
trackers = trackersStatisticsUseCase.getTrackers(it)
)
} ?: Effect.ErrorEffect("No appDesc.")
-
)
+ is Action.CloseQuickPrivacyDisabledMessage -> {
+ getQuickPrivacyStateUseCase.resetQuickPrivacyDisabledMessage()
+ flowOf(Effect.NoEffect)
+ }
}
},
singleEventProducer = { _, _, effect ->
when (effect) {
is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message)
- is Effect.QuickPrivacyDisabledWarningEffect ->
- SingleEvent.ErrorEvent(R.string.apptrackers_error_quickprivacy_disabled)
is Effect.NewStatisticsAvailablesEffect ->
SingleEvent.NewStatisticsAvailableSingleEvent
is Effect.OpenUrlEffect ->
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 d6edee6..7e606af 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
@@ -27,11 +27,13 @@ import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.snackbar.Snackbar
import foundation.e.flowmvi.MVIView
import foundation.e.privacycentralapp.DependencyContainer
import foundation.e.privacycentralapp.PrivacyCentralApplication
import foundation.e.privacycentralapp.R
import foundation.e.privacycentralapp.common.NavToolbarFragment
+import foundation.e.privacycentralapp.common.initQuickPrivacySnackbar
import foundation.e.privacycentralapp.databinding.ApptrackersFragmentBinding
import foundation.e.privacycentralapp.extensions.toText
import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf
@@ -65,6 +67,8 @@ class AppTrackersFragment :
private lateinit var binding: ApptrackersFragmentBinding
+ private var qpDisabledSnackbar: Snackbar? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launchWhenStarted {
@@ -109,10 +113,6 @@ class AppTrackersFragment :
viewModel.submitAction(Action.BlockAllToggleAction(binding.blockAllToggle.isChecked))
}
- binding.blockAllToggleClicker.setOnClickListener {
- viewModel.submitAction(Action.BlockAllToggleAction(false))
- }
-
binding.trackers.apply {
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
@@ -124,6 +124,10 @@ class AppTrackersFragment :
onClickTitle = { viewModel.submitAction(Action.ClickTracker(it)) }
)
}
+
+ qpDisabledSnackbar = initQuickPrivacySnackbar(binding.root) {
+ viewModel.submitAction(Action.CloseQuickPrivacyDisabledMessage)
+ }
}
override fun onResume() {
@@ -132,6 +136,9 @@ class AppTrackersFragment :
}
override fun render(state: State) {
+ if (state.showQuickPrivacyDisabledMessage) qpDisabledSnackbar?.show()
+ else qpDisabledSnackbar?.dismiss()
+
binding.trackersCountSummary.text = if (state.getTrackersCount() == 0) ""
else getString(
R.string.apptrackers_trackers_count_summary,
@@ -140,8 +147,6 @@ class AppTrackersFragment :
)
binding.blockAllToggle.isChecked = state.isBlockingActivated
- binding.blockAllToggle.isEnabled = state.isQuickPrivacyEnabled
- binding.blockAllToggleClicker.isVisible = !state.isQuickPrivacyEnabled
binding.trackersListTitle.isVisible = state.isBlockingActivated
@@ -151,7 +156,7 @@ class AppTrackersFragment :
binding.trackers.post {
(binding.trackers.adapter as ToggleTrackersAdapter?)?.updateDataSet(
trackersStatus,
- state.isBlockingActivated && state.isQuickPrivacyEnabled
+ state.isBlockingActivated
)
}
binding.noTrackersYet.isVisible = false
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt
index b9beccf..02a274a 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt
@@ -25,7 +25,6 @@ import android.view.ViewGroup
import android.widget.Switch
import android.widget.TextView
import androidx.core.content.ContextCompat
-import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import foundation.e.privacycentralapp.R
import foundation.e.privacymodules.trackers.Tracker
@@ -46,7 +45,6 @@ class ToggleTrackersAdapter(
val title: TextView = view.findViewById(R.id.title)
val toggle: Switch = view.findViewById(R.id.toggle)
- val toggleOverlay: View = view.findViewById(R.id.toggle_clicker)
fun bind(item: Pair<Tracker, Boolean>, isEnabled: Boolean) {
val text = item.first.label
@@ -62,14 +60,10 @@ class ToggleTrackersAdapter(
toggle.isChecked = item.second
toggle.isEnabled = isEnabled
- toggleOverlay.isVisible = !isEnabled
toggle.setOnClickListener {
onToggleSwitch(item.first, toggle.isChecked)
}
- toggleOverlay.setOnClickListener {
- onToggleSwitch(item.first, false)
- }
title.setOnClickListener { onClickTitle(item.first) }
}