Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 366e4ffa authored by Guillaume Jacquart's avatar Guillaume Jacquart
Browse files

Update IPScrambling UI

parent 74fb6729
Loading
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -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
@@ -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)
@@ -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)
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -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()
            }
+12 −12
Original line number Diff line number Diff line
@@ -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
@@ -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)
        }
    }

@@ -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)
        }
@@ -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)
    }
+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
                    }
                }
            })
    }
}
+27 −42
Original line number Diff line number Diff line
@@ -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())
Loading