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/internetprivacy/InternetPrivacyFeature.kt69
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt192
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt16
3 files changed, 107 insertions, 170 deletions
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 d8254b8..f7145d1 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
@@ -17,7 +17,6 @@
package foundation.e.privacycentralapp.features.internetprivacy
-import android.Manifest
import android.app.Activity
import android.content.Intent
import android.util.Log
@@ -26,18 +25,20 @@ import foundation.e.flowmvi.Reducer
import foundation.e.flowmvi.SingleEventProducer
import foundation.e.flowmvi.feature.BaseFeature
import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
+import foundation.e.privacycentralapp.domain.usecases.AppListUseCase
import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase
import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
-import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.ApplicationDescription
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.shareIn
// Define a state machine for Internet privacy feature
class InternetPrivacyFeature(
@@ -62,18 +63,8 @@ class InternetPrivacyFeature(
val availableLocationIds: List<String>,
val forceRedraw: Boolean = false
) {
-
- val isAllAppsScrambled get() = ipScrambledApps.isEmpty()
- fun getScrambledApps(): List<Pair<ApplicationDescription, Boolean>> {
- return availableApps
- .filter { it.packageName in ipScrambledApps }
- .map { it to true }
- }
-
fun getApps(): List<Pair<ApplicationDescription, Boolean>> {
- return availableApps
- .filter { it.packageName !in ipScrambledApps }
- .map { it to false }
+ return availableApps.map { it to (it.packageName in ipScrambledApps) }
}
val selectedLocationPosition get() = availableLocationIds.indexOf(selectedLocation)
@@ -102,7 +93,10 @@ class InternetPrivacyFeature(
object QuickPrivacyDisabledWarningEffect : Effect()
data class ShowAndroidVpnDisclaimerEffect(val intent: Intent) : Effect()
data class IpScrambledAppsUpdatedEffect(val ipScrambledApps: Collection<String>) : Effect()
- data class AvailableAppsListEffect(val apps: List<ApplicationDescription>) : Effect()
+ data class AvailableAppsListEffect(
+ val apps: List<ApplicationDescription>,
+ val ipScrambledApps: Collection<String>
+ ) : Effect()
data class LocationSelectedEffect(val locationId: String) : Effect()
data class AvailableCountriesEffect(val availableLocationsIds: List<String>) : Effect()
data class ErrorEffect(val message: String) : Effect()
@@ -119,16 +113,19 @@ class InternetPrivacyFeature(
),
coroutineScope: CoroutineScope,
ipScramblerModule: IIpScramblerModule,
- permissionsModule: PermissionsPrivacyModule,
getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- ipScramblingStateUseCase: IpScramblingStateUseCase
+ ipScramblingStateUseCase: IpScramblingStateUseCase,
+ appListUseCase: AppListUseCase
) = InternetPrivacyFeature(
initialState, coroutineScope,
reducer = { state, effect ->
when (effect) {
is Effect.ModeUpdatedEffect -> state.copy(mode = effect.mode)
is Effect.IpScrambledAppsUpdatedEffect -> state.copy(ipScrambledApps = effect.ipScrambledApps)
- is Effect.AvailableAppsListEffect -> state.copy(availableApps = effect.apps)
+ is Effect.AvailableAppsListEffect -> state.copy(
+ availableApps = effect.apps,
+ ipScrambledApps = effect.ipScrambledApps
+ )
is Effect.AvailableCountriesEffect -> state.copy(availableLocationIds = effect.availableLocationsIds)
is Effect.LocationSelectedEffect -> state.copy(selectedLocation = effect.locationId)
Effect.QuickPrivacyDisabledWarningEffect -> state.copy(forceRedraw = !state.forceRedraw)
@@ -139,33 +136,21 @@ class InternetPrivacyFeature(
when {
action is Action.LoadInternetModeAction -> merge(
getQuickPrivacyStateUseCase.quickPrivacyEnabledFlow.map { Effect.QuickPrivacyUpdatedEffect(it) },
- ipScramblingStateUseCase.internetPrivacyMode.map { Effect.ModeUpdatedEffect(it) },
+ flowOf(Effect.QuickPrivacyUpdatedEffect(true)),
+ ipScramblingStateUseCase.internetPrivacyMode.map { Effect.ModeUpdatedEffect(it) }.shareIn(scope = coroutineScope, started = SharingStarted.Lazily, replay = 0),
+ flowOf(Effect.ModeUpdatedEffect(InternetPrivacyMode.REAL_IP)),
flow {
- // TODO: filter deactivated apps"
- val apps = permissionsModule.getInstalledApplications()
- .filter {
- permissionsModule.getPermissions(it.packageName)
- .contains(Manifest.permission.INTERNET)
- }.map {
- it.icon = permissionsModule.getApplicationIcon(it.packageName)
- it
- }.sortedWith(object : Comparator<ApplicationDescription> {
- override fun compare(
- p0: ApplicationDescription?,
- p1: ApplicationDescription?
- ): Int {
- return if (p0?.icon != null && p1?.icon != null) {
- p0.label.toString().compareTo(p1.label.toString())
- } else if (p0?.icon == null) {
- 1
- } else {
- -1
- }
- }
- })
- emit(Effect.AvailableAppsListEffect(apps))
+ val apps = appListUseCase.getAppsUsingInternet()
+ if (ipScramblerModule.appList.isEmpty()) {
+ ipScramblerModule.appList = apps.map { it.packageName }.toMutableSet()
+ }
+ emit(
+ Effect.AvailableAppsListEffect(
+ apps,
+ ipScramblerModule.appList
+ )
+ )
},
- flowOf(Effect.IpScrambledAppsUpdatedEffect(ipScramblerModule.appList)),
flow {
val locationIds = mutableListOf("")
locationIds.addAll(ipScramblerModule.getAvailablesLocations().sorted())
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 3799349..e7a9480 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
@@ -18,20 +18,14 @@
package foundation.e.privacycentralapp.features.internetprivacy
import android.os.Bundle
-import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
-import android.widget.ProgressBar
-import android.widget.RadioButton
-import android.widget.Spinner
-import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
import foundation.e.flowmvi.MVIView
import foundation.e.privacycentralapp.DependencyContainer
import foundation.e.privacycentralapp.PrivacyCentralApplication
@@ -69,15 +63,10 @@ class InternetPrivacyFragment :
when (event) {
is InternetPrivacyFeature.SingleEvent.ErrorEvent -> {
displayToast(event.error)
- viewModel
}
is InternetPrivacyFeature.SingleEvent.StartAndroidVpnActivityEvent -> {
- Log.d("TestsVPN", event.intent.toString())
- Log.d("TestsVPN", event.intent.action.toString())
launchAndroidVpnDisclaimer.launch(event.intent)
}
- InternetPrivacyFeature.SingleEvent.HiddenIPSelectedEvent -> displayToast("Your IP is hidden")
- InternetPrivacyFeature.SingleEvent.RealIPSelectedEvent -> displayToast("Your IP is visible to internet")
}
}
}
@@ -99,134 +88,97 @@ class InternetPrivacyFragment :
super.onViewCreated(view, savedInstanceState)
binding = FragmentInternetActivityPolicyBinding.bind(view)
- listOf(R.id.recycler_view_scrambled, R.id.recycler_view_to_select).forEach { viewId ->
- view.findViewById<RecyclerView>(viewId)?.apply {
- layoutManager = LinearLayoutManager(requireContext())
- setHasFixedSize(true)
- adapter = ToggleAppsAdapter { packageName, isIpScrambled ->
- viewModel.submitAction(
- InternetPrivacyFeature.Action.ToggleAppIpScrambled(
- packageName,
- isIpScrambled
- )
+ binding.apps.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ setHasFixedSize(true)
+ adapter = ToggleAppsAdapter(R.layout.ipscrambling_item_app_toggle) { packageName, isIpScrambled ->
+ viewModel.submitAction(
+ InternetPrivacyFeature.Action.ToggleAppIpScrambled(
+ packageName,
+ isIpScrambled
)
- }
+ )
}
}
- bindClickListeners(view)
- }
-
- override fun getTitle(): String = getString(R.string.internet_activity_privacy)
+ binding.radioUseRealIp.container.setOnClickListener {
+ viewModel.submitAction(InternetPrivacyFeature.Action.UseRealIPAction)
+ }
- private fun bindClickListeners(fragmentView: View) {
- fragmentView.let {
- it.findViewById<RadioButton>(R.id.radio_use_real_ip)
- .setOnClickListener {
- viewModel.submitAction(InternetPrivacyFeature.Action.UseRealIPAction)
- }
- it.findViewById<RadioButton>(R.id.radio_use_hidden_ip)
- .setOnClickListener {
- viewModel.submitAction(InternetPrivacyFeature.Action.UseHiddenIPAction)
- }
+ binding.radioUseHiddenIp.container.setOnClickListener {
+ viewModel.submitAction(InternetPrivacyFeature.Action.UseHiddenIPAction)
}
}
+ override fun getTitle(): String = getString(R.string.ipscrambling_title)
+
override fun render(state: InternetPrivacyFeature.State) {
- view?.let {
- it.findViewById<RadioButton>(R.id.radio_use_hidden_ip).apply {
- isChecked = state.mode in listOf(
- InternetPrivacyMode.HIDE_IP,
- InternetPrivacyMode.HIDE_IP_LOADING
- )
- isEnabled = state.mode != InternetPrivacyMode.HIDE_IP_LOADING
- }
- it.findViewById<RadioButton>(R.id.radio_use_real_ip)?.apply {
- isChecked =
- state.mode in listOf(
- InternetPrivacyMode.REAL_IP,
- InternetPrivacyMode.REAL_IP_LOADING
- )
- isEnabled = state.mode != InternetPrivacyMode.REAL_IP_LOADING
- }
- it.findViewById<TextView>(R.id.ipscrambling_tor_status)?.apply {
- when (state.mode) {
- InternetPrivacyMode.HIDE_IP_LOADING -> {
- text = getString(R.string.ipscrambling_is_starting)
- visibility = View.VISIBLE
- }
- InternetPrivacyMode.REAL_IP_LOADING -> {
- text = getString(R.string.ipscrambling_is_stopping)
- visibility = View.VISIBLE
- }
- else -> {
- text = ""
- visibility = View.GONE
+ binding.radioUseHiddenIp.radiobutton.apply {
+ isChecked = state.mode in listOf(
+ InternetPrivacyMode.HIDE_IP,
+ InternetPrivacyMode.HIDE_IP_LOADING
+ )
+ isEnabled = state.mode != InternetPrivacyMode.HIDE_IP_LOADING
+ }
+ binding.radioUseRealIp.radiobutton.apply {
+ isChecked =
+ state.mode in listOf(
+ InternetPrivacyMode.REAL_IP,
+ InternetPrivacyMode.REAL_IP_LOADING
+ )
+ isEnabled = state.mode != InternetPrivacyMode.REAL_IP_LOADING
+ }
+
+ binding.ipscramblingSelectLocation.apply {
+ adapter = ArrayAdapter(
+ requireContext(), android.R.layout.simple_spinner_item,
+ state.availableLocationIds.map {
+ if (it == "") {
+ getString(R.string.ipscrambling_any_location)
+ } else {
+ Locale("", it).displayCountry
}
}
+ ).apply {
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
- it.findViewById<Spinner>(R.id.ipscrambling_select_location)?.apply {
- adapter = ArrayAdapter(
- requireContext(), android.R.layout.simple_spinner_item,
- state.availableLocationIds.map {
- if (it == "") {
- getString(R.string.ipscrambling_any_location)
- } else {
- Locale("", it).displayCountry
- }
- }
- ).apply {
- setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ setOnItemSelectedListener(object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View, position: Int, id: Long) {
+ viewModel.submitAction(InternetPrivacyFeature.Action.SelectLocationAction(position))
}
- setOnItemSelectedListener(object : AdapterView.OnItemSelectedListener {
- override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View, position: Int, id: Long) {
- viewModel.submitAction(InternetPrivacyFeature.Action.SelectLocationAction(position))
- }
+ override fun onNothingSelected(parentView: AdapterView<*>?) {}
+ })
- override fun onNothingSelected(parentView: AdapterView<*>?) {}
- })
-
- setSelection(state.selectedLocationPosition)
- }
-
- it.findViewById<TextView>(R.id.ipscrambling_activated)?.apply {
- text = getString(
- if (state.isAllAppsScrambled) R.string.ipscrambling_all_apps_scrambled
- else R.string.ipscrambling_only_selected_apps_scrambled
- )
- }
+ setSelection(state.selectedLocationPosition)
+ }
- it.findViewById<RecyclerView>(R.id.recycler_view_scrambled)?.apply {
- (adapter as ToggleAppsAdapter?)?.dataSet = state.getScrambledApps()
- }
- it.findViewById<RecyclerView>(R.id.recycler_view_to_select)?.apply {
- (adapter as ToggleAppsAdapter?)?.dataSet = state.getApps()
- }
+ // TODO: this should not be mandatory.
+ binding.apps.post {
+ (binding.apps.adapter as ToggleAppsAdapter?)?.dataSet = state.getApps()
+ }
- val viewIdsToHide = listOf(
- R.id.ipscrambling_activated,
- R.id.recycler_view_scrambled,
- R.id.ipscrambling_select_apps,
- R.id.recycler_view_to_select,
- R.id.ipscrambling_location
+ val viewIdsToHide = listOf(
+ binding.ipscramblingLocationLabel,
+ binding.selectLocationContainer,
+ binding.ipscramblingSelectLocation,
+ binding.ipscramblingSelectApps,
+ binding.apps
+ )
+
+ when {
+ state.mode in listOf(
+ InternetPrivacyMode.HIDE_IP_LOADING,
+ InternetPrivacyMode.REAL_IP_LOADING
)
- val progressBar = it.findViewById<ProgressBar>(R.id.ipscrambling_loading)
-
- when {
- state.mode in listOf(
- InternetPrivacyMode.HIDE_IP_LOADING,
- InternetPrivacyMode.REAL_IP_LOADING
- )
- || state.availableApps.isEmpty() -> {
- progressBar?.visibility = View.VISIBLE
- viewIdsToHide.forEach { viewId -> it.findViewById<View>(viewId)?.visibility = View.GONE }
- }
- else -> {
- progressBar?.visibility = View.GONE
- viewIdsToHide.forEach { viewId -> it.findViewById<View>(viewId)?.visibility = View.VISIBLE }
- }
+ || state.availableApps.isEmpty() -> {
+ binding.loader.visibility = View.VISIBLE
+ viewIdsToHide.forEach { it.visibility = View.GONE }
+ }
+ else -> {
+ binding.loader.visibility = View.GONE
+ viewIdsToHide.forEach { it.visibility = View.VISIBLE }
}
}
}
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 6f3c200..2ffa92e 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
@@ -20,19 +20,19 @@ package foundation.e.privacycentralapp.features.internetprivacy
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import foundation.e.privacycentralapp.common.Factory
+import foundation.e.privacycentralapp.domain.usecases.AppListUseCase
import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase
import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
-import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
class InternetPrivacyViewModel(
private val ipScramblerModule: IIpScramblerModule,
- private val permissionsModule: PermissionsPrivacyModule,
private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- private val ipScramblingStateUseCase: IpScramblingStateUseCase
+ private val ipScramblingStateUseCase: IpScramblingStateUseCase,
+ private val appListUseCase: AppListUseCase
) : ViewModel() {
private val _actions = MutableSharedFlow<InternetPrivacyFeature.Action>()
@@ -42,9 +42,9 @@ class InternetPrivacyViewModel(
InternetPrivacyFeature.create(
coroutineScope = viewModelScope,
ipScramblerModule = ipScramblerModule,
- permissionsModule = permissionsModule,
getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase,
- ipScramblingStateUseCase = ipScramblingStateUseCase
+ ipScramblingStateUseCase = ipScramblingStateUseCase,
+ appListUseCase = appListUseCase
)
}
@@ -57,12 +57,12 @@ class InternetPrivacyViewModel(
class InternetPrivacyViewModelFactory(
private val ipScramblerModule: IIpScramblerModule,
- private val permissionsModule: PermissionsPrivacyModule,
private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- private val ipScramblingStateUseCase: IpScramblingStateUseCase
+ private val ipScramblingStateUseCase: IpScramblingStateUseCase,
+ private val appListUseCase: AppListUseCase
) :
Factory<InternetPrivacyViewModel> {
override fun create(): InternetPrivacyViewModel {
- return InternetPrivacyViewModel(ipScramblerModule, permissionsModule, getQuickPrivacyStateUseCase, ipScramblingStateUseCase)
+ return InternetPrivacyViewModel(ipScramblerModule, getQuickPrivacyStateUseCase, ipScramblingStateUseCase, appListUseCase)
}
}