summaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2021-08-31 15:44:35 +0000
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2021-08-31 15:44:35 +0000
commitc8a8c2b13b6584c696ca3a5ae8aad843ea5de185 (patch)
treef41acb8f401ff2d2cfd946f91bd0b6da0c1f0f43 /app/src/main/java
parentdaea2f9510ac1af22a4e2e2f3db7c2d6d314008b (diff)
parent5d0524a838149fda58c64c83ce0adfd64db0e96a (diff)
Merge branch 'feature/ipscrambling' into 'master'
Feature/ipscrambling See merge request e/privacy-central/privacycentralapp!7
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt8
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt73
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt210
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt163
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt24
5 files changed, 422 insertions, 56 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
index fcc2eaa..1ab848c 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
@@ -20,8 +20,11 @@ package foundation.e.privacycentralapp
import android.app.Application
import android.content.Context
import android.os.Process
+import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyViewModelFactory
import foundation.e.privacycentralapp.features.location.FakeLocationViewModelFactory
import foundation.e.privacycentralapp.features.location.LocationApiDelegate
+import foundation.e.privacymodules.ipscrambler.IpScramblerModule
+import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
import foundation.e.privacymodules.location.FakeLocation
import foundation.e.privacymodules.location.IFakeLocation
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
@@ -39,6 +42,7 @@ class DependencyContainer constructor(val app: Application) {
private val fakeLocationModule: IFakeLocation by lazy { FakeLocation(app.applicationContext) }
private val permissionsModule by lazy { PermissionsPrivacyModule(app.applicationContext) }
+ private val ipScramblerModule: IIpScramblerModule by lazy { IpScramblerModule(app.applicationContext) }
private val appDesc by lazy {
ApplicationDescription(
@@ -58,4 +62,8 @@ class DependencyContainer constructor(val app: Application) {
}
val blockerService = BlockerInterface.getInstance(context)
+
+ val internetPrivacyViewModelFactory by lazy {
+ InternetPrivacyViewModelFactory(ipScramblerModule, permissionsModule)
+ }
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt
new file mode 100644
index 0000000..4f9a6fc
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package foundation.e.privacycentralapp.common
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.Switch
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import foundation.e.privacycentralapp.R
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+
+open class ToggleAppsAdapter(
+ private val listener: (String, Boolean) -> Unit
+) :
+ RecyclerView.Adapter<ToggleAppsAdapter.PermissionViewHolder>() {
+
+ class PermissionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val appName: TextView = view.findViewById(R.id.app_title)
+
+ @SuppressLint("UseSwitchCompatOrMaterialCode")
+ val togglePermission: Switch = view.findViewById(R.id.toggle)
+
+ fun bind(item: Pair<ApplicationDescription, Boolean>) {
+ appName.text = item.first.label
+ togglePermission.isChecked = item.second
+
+ itemView.findViewById<ImageView>(R.id.app_icon).setImageDrawable(item.first.icon)
+ }
+ }
+
+ var dataSet: List<Pair<ApplicationDescription, Boolean>> = emptyList()
+ set(value) {
+ field = value
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_app_toggle, parent, false)
+ val holder = PermissionViewHolder(view)
+ holder.togglePermission.setOnCheckedChangeListener { _, isChecked ->
+ listener(dataSet[holder.adapterPosition].first.packageName, isChecked)
+ }
+ view.findViewById<Switch>(R.id.toggle)
+ return holder
+ }
+
+ override fun onBindViewHolder(holder: PermissionViewHolder, position: Int) {
+ val permission = dataSet[position]
+ holder.bind(permission)
+ }
+
+ override fun getItemCount(): Int = dataSet.size
+}
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 b34024e..41ce9ad 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,16 +17,25 @@
package foundation.e.privacycentralapp.features.internetprivacy
+import android.Manifest
+import android.app.Activity
+import android.content.Intent
import android.util.Log
import foundation.e.flowmvi.Actor
import foundation.e.flowmvi.Reducer
import foundation.e.flowmvi.SingleEventProducer
import foundation.e.flowmvi.feature.BaseFeature
-import foundation.e.privacycentralapp.dummy.DummyDataSource
-import foundation.e.privacycentralapp.dummy.InternetPrivacyMode
+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.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.merge
// Define a state machine for Internet privacy feature
class InternetPrivacyFeature(
@@ -43,11 +52,34 @@ class InternetPrivacyFeature(
{ message -> Log.d("InternetPrivacyFeature", message) },
singleEventProducer
) {
- data class State(val mode: InternetPrivacyMode)
+ data class State(
+ val mode: IIpScramblerModule.Status,
+ val availableApps: List<ApplicationDescription>,
+ val ipScrambledApps: Collection<String>,
+ val selectedLocation: String,
+ val availableLocationIds: List<String>
+ ) {
+
+ 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 }
+ }
+
+ val selectedLocationPosition get() = availableLocationIds.indexOf(selectedLocation)
+ }
sealed class SingleEvent {
object RealIPSelectedEvent : SingleEvent()
object HiddenIPSelectedEvent : SingleEvent()
+ data class StartAndroidVpnActivityEvent(val intent: Intent) : SingleEvent()
data class ErrorEvent(val error: String) : SingleEvent()
}
@@ -55,53 +87,171 @@ class InternetPrivacyFeature(
object LoadInternetModeAction : Action()
object UseRealIPAction : Action()
object UseHiddenIPAction : Action()
+ data class AndroidVpnActivityResultAction(val resultCode: Int) : Action()
+ data class ToggleAppIpScrambled(val packageName: String, val isIpScrambled: Boolean) : Action()
+ data class SelectLocationAction(val position: Int) : Action()
}
sealed class Effect {
- data class ModeUpdatedEffect(val mode: InternetPrivacyMode) : Effect()
+ data class ModeUpdatedEffect(val mode: IIpScramblerModule.Status) : Effect()
+ object NoEffect : 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 LocationSelectedEffect(val locationId: String) : Effect()
+ data class AvailableCountriesEffect(val availableLocationsIds: List<String>) : Effect()
data class ErrorEffect(val message: String) : Effect()
}
companion object {
fun create(
- initialState: State = State(InternetPrivacyMode.REAL_IP),
- coroutineScope: CoroutineScope
+ initialState: State = State(
+ IIpScramblerModule.Status.STOPPING,
+ availableApps = emptyList(),
+ ipScrambledApps = emptyList(),
+ availableLocationIds = emptyList(),
+ selectedLocation = ""
+ ),
+ coroutineScope: CoroutineScope,
+ ipScramblerModule: IIpScramblerModule,
+ permissionsModule: PermissionsPrivacyModule
) = InternetPrivacyFeature(
initialState, coroutineScope,
reducer = { state, effect ->
when (effect) {
is Effect.ModeUpdatedEffect -> state.copy(mode = effect.mode)
- is Effect.ErrorEffect -> state
+ is Effect.IpScrambledAppsUpdatedEffect -> state.copy(ipScrambledApps = effect.ipScrambledApps)
+ is Effect.AvailableAppsListEffect -> state.copy(availableApps = effect.apps)
+ is Effect.AvailableCountriesEffect -> state.copy(availableLocationIds = effect.availableLocationsIds)
+ is Effect.LocationSelectedEffect -> state.copy(selectedLocation = effect.locationId)
+ else -> state
}
},
- actor = { _, action ->
- when (action) {
- Action.LoadInternetModeAction -> flowOf(Effect.ModeUpdatedEffect(DummyDataSource.internetActivityMode.value))
- Action.UseHiddenIPAction, Action.UseRealIPAction -> flow {
- val success =
- DummyDataSource.setInternetPrivacyMode(if (action is Action.UseHiddenIPAction) InternetPrivacyMode.HIDE_IP else InternetPrivacyMode.REAL_IP)
- emit(
- if (success) Effect.ModeUpdatedEffect(DummyDataSource.internetActivityMode.value) else Effect.ErrorEffect(
- "Couldn't update internet mode"
- )
- )
- }
- }
- },
- singleEventProducer = { _, action, effect ->
- when (action) {
- Action.UseRealIPAction, Action.UseHiddenIPAction -> when (effect) {
- is Effect.ModeUpdatedEffect -> {
- if (effect.mode == InternetPrivacyMode.REAL_IP) {
- SingleEvent.RealIPSelectedEvent
+ actor = { state, action ->
+ when {
+ action is Action.LoadInternetModeAction -> merge(
+ callbackFlow {
+ val listener = object : IIpScramblerModule.Listener {
+ override fun onStatusChanged(newStatus: IIpScramblerModule.Status) {
+ offer(Effect.ModeUpdatedEffect(newStatus))
+ }
+
+ override fun log(message: String) {}
+ override fun onTrafficUpdate(upload: Long, download: Long, read: Long, write: Long) {}
+ }
+ ipScramblerModule.addListener(listener)
+ ipScramblerModule.requestStatus()
+ awaitClose { ipScramblerModule.removeListener(listener) }
+ },
+ 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))
+ },
+ flowOf(Effect.IpScrambledAppsUpdatedEffect(ipScramblerModule.appList)),
+ flow {
+ val locationIds = mutableListOf("")
+ locationIds.addAll(ipScramblerModule.getAvailablesLocations().sorted())
+ emit(Effect.AvailableCountriesEffect(locationIds))
+ },
+ flowOf(Effect.LocationSelectedEffect(ipScramblerModule.exitCountry))
+ ).flowOn(Dispatchers.Default)
+ action is Action.AndroidVpnActivityResultAction ->
+ if (action.resultCode == Activity.RESULT_OK) {
+ if (state.mode in listOf(
+ IIpScramblerModule.Status.OFF,
+ IIpScramblerModule.Status.STOPPING
+ )
+ ) {
+ ipScramblerModule.start()
+ flowOf(Effect.ModeUpdatedEffect(IIpScramblerModule.Status.STARTING))
} else {
- SingleEvent.HiddenIPSelectedEvent
+ flowOf(Effect.ErrorEffect("Vpn already started"))
}
+ } else {
+ flowOf(Effect.ErrorEffect("Vpn wasn't allowed to start"))
}
- is Effect.ErrorEffect -> {
- SingleEvent.ErrorEvent(effect.message)
+
+ action is Action.UseRealIPAction && state.mode in listOf(
+ IIpScramblerModule.Status.ON,
+ IIpScramblerModule.Status.STARTING,
+ IIpScramblerModule.Status.STOPPING
+ ) -> {
+ ipScramblerModule.stop()
+ flowOf(Effect.ModeUpdatedEffect(IIpScramblerModule.Status.STOPPING))
+ }
+ action is Action.UseHiddenIPAction
+ && state.mode in listOf(
+ IIpScramblerModule.Status.OFF,
+ IIpScramblerModule.Status.STOPPING
+ ) -> {
+ ipScramblerModule.prepareAndroidVpn()?.let {
+ flowOf(Effect.ShowAndroidVpnDisclaimerEffect(it))
+ } ?: run {
+ ipScramblerModule.start()
+ flowOf(Effect.ModeUpdatedEffect(IIpScramblerModule.Status.STARTING))
}
}
+
+ action is Action.ToggleAppIpScrambled -> {
+ val ipScrambledApps = mutableSetOf<String>()
+ ipScrambledApps.addAll(ipScramblerModule.appList)
+ if (action.isIpScrambled) {
+ ipScrambledApps.add(action.packageName)
+ } else {
+ ipScrambledApps.remove(action.packageName)
+ }
+ ipScramblerModule.appList = ipScrambledApps
+ flowOf(Effect.IpScrambledAppsUpdatedEffect(ipScrambledApps = ipScrambledApps))
+ }
+ action is Action.SelectLocationAction -> {
+ val locationId = state.availableLocationIds[action.position]
+ ipScramblerModule.exitCountry = locationId
+ flowOf(Effect.LocationSelectedEffect(locationId))
+ }
+ else -> flowOf(Effect.NoEffect)
+ }
+ },
+ singleEventProducer = { _, action, effect ->
+ when {
+ effect is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message)
+
+ action is Action.UseHiddenIPAction
+ && effect is Effect.ShowAndroidVpnDisclaimerEffect ->
+ SingleEvent.StartAndroidVpnActivityEvent(effect.intent)
+
+ // Action.UseRealIPAction, Action.UseHiddenIPAction -> when (effect) {
+ // is Effect.ModeUpdatedEffect -> {
+ // if (effect.mode == InternetPrivacyMode.REAL_IP) {
+ // SingleEvent.RealIPSelectedEvent
+ // } else {
+ // SingleEvent.HiddenIPSelectedEvent
+ // }
+ // }
+ // is Effect.ErrorEffect -> {
+ // SingleEvent.ErrorEvent(effect.message)
+ // }
+ // }
else -> null
}
}
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 5baae81..22e63e3 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
@@ -19,22 +19,41 @@ package foundation.e.privacycentralapp.features.internetprivacy
import android.os.Bundle
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
import foundation.e.privacycentralapp.R
import foundation.e.privacycentralapp.common.NavToolbarFragment
-import foundation.e.privacycentralapp.dummy.InternetPrivacyMode
+import foundation.e.privacycentralapp.common.ToggleAppsAdapter
+import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf
+import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
+import java.util.Locale
class InternetPrivacyFragment :
NavToolbarFragment(R.layout.fragment_internet_activity_policy),
MVIView<InternetPrivacyFeature.State, InternetPrivacyFeature.Action> {
- private val viewModel: InternetPrivacyViewModel by viewModels()
+ private val dependencyContainer: DependencyContainer by lazy {
+ (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer
+ }
+
+ private val viewModel: InternetPrivacyViewModel by viewModels {
+ viewModelProviderFactoryOf { dependencyContainer.internetPrivacyViewModelFactory.create() }
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -45,6 +64,8 @@ class InternetPrivacyFragment :
viewModel.internetPrivacyFeature.singleEvents.collect { event ->
when (event) {
is InternetPrivacyFeature.SingleEvent.ErrorEvent -> displayToast(event.error)
+ is InternetPrivacyFeature.SingleEvent.StartAndroidVpnActivityEvent ->
+ launchAndroidVpnDisclaimer.launch(event.intent)
InternetPrivacyFeature.SingleEvent.HiddenIPSelectedEvent -> displayToast("Your IP is hidden")
InternetPrivacyFeature.SingleEvent.RealIPSelectedEvent -> displayToast("Your IP is visible to internet")
}
@@ -60,8 +81,28 @@ class InternetPrivacyFragment :
.show()
}
+ private val launchAndroidVpnDisclaimer = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ viewModel.submitAction(InternetPrivacyFeature.Action.AndroidVpnActivityResultAction(it.resultCode))
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+
+ 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
+ )
+ )
+ }
+ }
+ }
+
bindClickListeners(view)
}
@@ -70,38 +111,112 @@ class InternetPrivacyFragment :
private fun bindClickListeners(fragmentView: View) {
fragmentView.let {
it.findViewById<RadioButton>(R.id.radio_use_real_ip)
- .setOnClickListener { radioButton ->
- toggleIP(radioButton)
+ .setOnClickListener {
+ viewModel.submitAction(InternetPrivacyFeature.Action.UseRealIPAction)
}
it.findViewById<RadioButton>(R.id.radio_use_hidden_ip)
- .setOnClickListener { radioButton ->
- toggleIP(radioButton)
+ .setOnClickListener {
+ viewModel.submitAction(InternetPrivacyFeature.Action.UseHiddenIPAction)
}
}
}
- private fun toggleIP(radioButton: View?) {
- if (radioButton is RadioButton) {
- val checked = radioButton.isChecked
- when (radioButton.id) {
- R.id.radio_use_real_ip ->
- if (checked) {
- viewModel.submitAction(InternetPrivacyFeature.Action.UseRealIPAction)
+ override fun render(state: InternetPrivacyFeature.State) {
+ view?.let {
+ it.findViewById<RadioButton>(R.id.radio_use_hidden_ip).apply {
+ isChecked = state.mode in listOf(
+ IIpScramblerModule.Status.ON,
+ IIpScramblerModule.Status.STARTING
+ )
+ isEnabled = state.mode != IIpScramblerModule.Status.STARTING
+ }
+ it.findViewById<RadioButton>(R.id.radio_use_real_ip)?.apply {
+ isChecked =
+ state.mode in listOf(
+ IIpScramblerModule.Status.OFF,
+ IIpScramblerModule.Status.STOPPING
+ )
+ isEnabled = state.mode != IIpScramblerModule.Status.STOPPING
+ }
+ it.findViewById<TextView>(R.id.ipscrambling_tor_status)?.apply {
+ when (state.mode) {
+ IIpScramblerModule.Status.STARTING -> {
+ text = getString(R.string.ipscrambling_is_starting)
+ visibility = View.VISIBLE
+ }
+ IIpScramblerModule.Status.STOPPING -> {
+ text = getString(R.string.ipscrambling_is_stopping)
+ visibility = View.VISIBLE
}
- R.id.radio_use_hidden_ip ->
- if (checked) {
- viewModel.submitAction(InternetPrivacyFeature.Action.UseHiddenIPAction)
+ else -> {
+ text = ""
+ visibility = View.GONE
}
+ }
}
- }
- }
- override fun render(state: InternetPrivacyFeature.State) {
- view?.let {
- it.findViewById<RadioButton>(R.id.radio_use_hidden_ip).isChecked =
- state.mode == InternetPrivacyMode.HIDE_IP
- it.findViewById<RadioButton>(R.id.radio_use_real_ip).isChecked =
- state.mode == InternetPrivacyMode.REAL_IP
+ 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))
+ }
+
+ 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
+ )
+ }
+
+ 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()
+ }
+
+ 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 progressBar = it.findViewById<ProgressBar>(R.id.ipscrambling_loading)
+
+ when {
+ state.mode in listOf(
+ IIpScramblerModule.Status.STARTING,
+ IIpScramblerModule.Status.STOPPING
+ )
+ || 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 }
+ }
+ }
}
}
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 b66b611..a6455ee 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
@@ -19,17 +19,27 @@ package foundation.e.privacycentralapp.features.internetprivacy
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import foundation.e.privacycentralapp.common.Factory
+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 : ViewModel() {
+class InternetPrivacyViewModel(
+ private val ipScramblerModule: IIpScramblerModule,
+ private val permissionsModule: PermissionsPrivacyModule
+) : ViewModel() {
private val _actions = MutableSharedFlow<InternetPrivacyFeature.Action>()
val actions = _actions.asSharedFlow()
val internetPrivacyFeature: InternetPrivacyFeature by lazy {
- InternetPrivacyFeature.create(coroutineScope = viewModelScope)
+ InternetPrivacyFeature.create(
+ coroutineScope = viewModelScope,
+ ipScramblerModule = ipScramblerModule,
+ permissionsModule = permissionsModule
+ )
}
fun submitAction(action: InternetPrivacyFeature.Action) {
@@ -38,3 +48,13 @@ class InternetPrivacyViewModel : ViewModel() {
}
}
}
+
+class InternetPrivacyViewModelFactory(
+ private val ipScramblerModule: IIpScramblerModule,
+ private val permissionsModule: PermissionsPrivacyModule
+) :
+ Factory<InternetPrivacyViewModel> {
+ override fun create(): InternetPrivacyViewModel {
+ return InternetPrivacyViewModel(ipScramblerModule, permissionsModule)
+ }
+}