Loading app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +5 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.app.Application import android.content.Context import android.os.Process import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.usecases.AppListUseCase import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase import foundation.e.privacycentralapp.features.dashboard.DashBoardViewModelFactory Loading Loading @@ -73,6 +74,9 @@ class DependencyContainer constructor(val app: Application) { private val ipScramblingStateUseCase by lazy { IpScramblingStateUseCase(ipScramblerModule, localStateRepository, GlobalScope) } private val appListUseCase by lazy { AppListUseCase(permissionsModule) } val dashBoardViewModelFactory by lazy { DashBoardViewModelFactory(getQuickPrivacyStateUseCase, ipScramblingStateUseCase) Loading @@ -85,6 +89,6 @@ class DependencyContainer constructor(val app: Application) { val blockerService = BlockerInterface.getInstance(context) val internetPrivacyViewModelFactory by lazy { InternetPrivacyViewModelFactory(ipScramblerModule, permissionsModule, getQuickPrivacyStateUseCase, ipScramblingStateUseCase) InternetPrivacyViewModelFactory(ipScramblerModule, getQuickPrivacyStateUseCase, ipScramblingStateUseCase, appListUseCase) } } app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt +2 −1 Original line number Diff line number Diff line Loading @@ -19,13 +19,14 @@ package foundation.e.privacycentralapp.common import androidx.annotation.LayoutRes import com.google.android.material.appbar.MaterialToolbar import foundation.e.privacycentralapp.R abstract class NavToolbarFragment(@LayoutRes contentLayoutId: Int) : ToolbarFragment(contentLayoutId) { override fun setupToolbar(toolbar: MaterialToolbar) { super.setupToolbar(toolbar) toolbar.apply { setNavigationIcon(lineageos.platform.R.drawable.ic_back) setNavigationIcon(R.drawable.ic_ic_chevron_left_24dp) setNavigationOnClickListener { requireActivity().onBackPressed() } Loading app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt +12 −12 Original line number Diff line number Diff line Loading @@ -17,10 +17,10 @@ package foundation.e.privacycentralapp.common import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CheckBox import android.widget.ImageView import android.widget.Switch import android.widget.TextView Loading @@ -28,22 +28,22 @@ import androidx.recyclerview.widget.RecyclerView import foundation.e.privacycentralapp.R import foundation.e.privacymodules.permissions.data.ApplicationDescription open class ToggleAppsAdapter( class ToggleAppsAdapter( private val itemsLayout: Int, private val listener: (String, Boolean) -> Unit ) : RecyclerView.Adapter<ToggleAppsAdapter.PermissionViewHolder>() { RecyclerView.Adapter<ToggleAppsAdapter.ViewHolder>() { class PermissionViewHolder(view: View) : RecyclerView.ViewHolder(view) { val appName: TextView = view.findViewById(R.id.app_title) class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val appName: TextView = view.findViewById(R.id.title) @SuppressLint("UseSwitchCompatOrMaterialCode") val togglePermission: Switch = view.findViewById(R.id.toggle) val togglePermission: CheckBox = 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) itemView.findViewById<ImageView>(R.id.icon).setImageDrawable(item.first.icon) } } Loading @@ -53,10 +53,10 @@ open class ToggleAppsAdapter( notifyDataSetChanged() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_app_toggle, parent, false) val holder = PermissionViewHolder(view) .inflate(itemsLayout, parent, false) val holder = ViewHolder(view) holder.togglePermission.setOnCheckedChangeListener { _, isChecked -> listener(dataSet[holder.adapterPosition].first.packageName, isChecked) } Loading @@ -64,7 +64,7 @@ open class ToggleAppsAdapter( return holder } override fun onBindViewHolder(holder: PermissionViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) { val permission = dataSet[position] holder.bind(permission) } Loading app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * 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.domain.usecases import android.Manifest import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.ApplicationDescription class AppListUseCase( private val permissionsModule: PermissionsPrivacyModule ) { fun getAppsUsingInternet(): List<ApplicationDescription> { return 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 } } }) } } app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt +27 −42 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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( Loading @@ -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) Loading Loading @@ -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() Loading @@ -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) Loading @@ -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()) Loading Loading
app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +5 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.app.Application import android.content.Context import android.os.Process import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.usecases.AppListUseCase import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase import foundation.e.privacycentralapp.features.dashboard.DashBoardViewModelFactory Loading Loading @@ -73,6 +74,9 @@ class DependencyContainer constructor(val app: Application) { private val ipScramblingStateUseCase by lazy { IpScramblingStateUseCase(ipScramblerModule, localStateRepository, GlobalScope) } private val appListUseCase by lazy { AppListUseCase(permissionsModule) } val dashBoardViewModelFactory by lazy { DashBoardViewModelFactory(getQuickPrivacyStateUseCase, ipScramblingStateUseCase) Loading @@ -85,6 +89,6 @@ class DependencyContainer constructor(val app: Application) { val blockerService = BlockerInterface.getInstance(context) val internetPrivacyViewModelFactory by lazy { InternetPrivacyViewModelFactory(ipScramblerModule, permissionsModule, getQuickPrivacyStateUseCase, ipScramblingStateUseCase) InternetPrivacyViewModelFactory(ipScramblerModule, getQuickPrivacyStateUseCase, ipScramblingStateUseCase, appListUseCase) } }
app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt +2 −1 Original line number Diff line number Diff line Loading @@ -19,13 +19,14 @@ package foundation.e.privacycentralapp.common import androidx.annotation.LayoutRes import com.google.android.material.appbar.MaterialToolbar import foundation.e.privacycentralapp.R abstract class NavToolbarFragment(@LayoutRes contentLayoutId: Int) : ToolbarFragment(contentLayoutId) { override fun setupToolbar(toolbar: MaterialToolbar) { super.setupToolbar(toolbar) toolbar.apply { setNavigationIcon(lineageos.platform.R.drawable.ic_back) setNavigationIcon(R.drawable.ic_ic_chevron_left_24dp) setNavigationOnClickListener { requireActivity().onBackPressed() } Loading
app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt +12 −12 Original line number Diff line number Diff line Loading @@ -17,10 +17,10 @@ package foundation.e.privacycentralapp.common import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CheckBox import android.widget.ImageView import android.widget.Switch import android.widget.TextView Loading @@ -28,22 +28,22 @@ import androidx.recyclerview.widget.RecyclerView import foundation.e.privacycentralapp.R import foundation.e.privacymodules.permissions.data.ApplicationDescription open class ToggleAppsAdapter( class ToggleAppsAdapter( private val itemsLayout: Int, private val listener: (String, Boolean) -> Unit ) : RecyclerView.Adapter<ToggleAppsAdapter.PermissionViewHolder>() { RecyclerView.Adapter<ToggleAppsAdapter.ViewHolder>() { class PermissionViewHolder(view: View) : RecyclerView.ViewHolder(view) { val appName: TextView = view.findViewById(R.id.app_title) class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val appName: TextView = view.findViewById(R.id.title) @SuppressLint("UseSwitchCompatOrMaterialCode") val togglePermission: Switch = view.findViewById(R.id.toggle) val togglePermission: CheckBox = 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) itemView.findViewById<ImageView>(R.id.icon).setImageDrawable(item.first.icon) } } Loading @@ -53,10 +53,10 @@ open class ToggleAppsAdapter( notifyDataSetChanged() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_app_toggle, parent, false) val holder = PermissionViewHolder(view) .inflate(itemsLayout, parent, false) val holder = ViewHolder(view) holder.togglePermission.setOnCheckedChangeListener { _, isChecked -> listener(dataSet[holder.adapterPosition].first.packageName, isChecked) } Loading @@ -64,7 +64,7 @@ open class ToggleAppsAdapter( return holder } override fun onBindViewHolder(holder: PermissionViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) { val permission = dataSet[position] holder.bind(permission) } Loading
app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * 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.domain.usecases import android.Manifest import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.ApplicationDescription class AppListUseCase( private val permissionsModule: PermissionsPrivacyModule ) { fun getAppsUsingInternet(): List<ApplicationDescription> { return 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 } } }) } }
app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt +27 −42 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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( Loading @@ -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) Loading Loading @@ -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() Loading @@ -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) Loading @@ -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()) Loading