diff options
Diffstat (limited to 'app/src/main/java/foundation/e/privacycentralapp/features')
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) } } |
