diff options
author | jacquarg <guillaume.jacquart@hoodbrains.com> | 2021-10-29 16:44:39 +0200 |
---|---|---|
committer | jacquarg <guillaume.jacquart@hoodbrains.com> | 2021-10-29 18:00:47 +0200 |
commit | 366e4ffa04e8d301794e613b89ed918df0b59517 (patch) | |
tree | e956d7f30128ef6f5bdd2494be288ee1cb41ce20 /app/src/main | |
parent | 74fb672978043886e261eb66c47658caf05812bb (diff) |
Update IPScrambling UI
Diffstat (limited to 'app/src/main')
14 files changed, 371 insertions, 302 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index 094ec3e..1f7cd3d 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -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) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt index 52197cd..5930fff 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/common/NavToolbarFragment.kt @@ -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() } diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt index 4f9a6fc..71b5e97 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/common/ToggleAppsAdapter.kt @@ -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) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt new file mode 100644 index 0000000..8ce08a3 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt @@ -0,0 +1,51 @@ +/* + * 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 + } + } + }) + } +} 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) } } diff --git a/app/src/main/res/drawable/ic_ic_chevron_left_24dp.xml b/app/src/main/res/drawable/ic_ic_chevron_left_24dp.xml new file mode 100644 index 0000000..e88ab33 --- /dev/null +++ b/app/src/main/res/drawable/ic_ic_chevron_left_24dp.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M17.885,3.77L16.115,2L6.115,12L16.115,22L17.885,20.23L9.655,12L17.885,3.77Z" + android:fillColor="#000000" + android:fillAlpha="0.87"/> +</vector> diff --git a/app/src/main/res/drawable/outlined_background.xml b/app/src/main/res/drawable/outlined_background.xml new file mode 100644 index 0000000..2040f18 --- /dev/null +++ b/app/src/main/res/drawable/outlined_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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/>. + --> + + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <stroke android:width="1dp" android:color="@color/grey_divider" /> + <corners android:radius="4dp" /> +</shape> diff --git a/app/src/main/res/layout/fragment_internet_activity_policy.xml b/app/src/main/res/layout/fragment_internet_activity_policy.xml index 982b7c4..4f29776 100644 --- a/app/src/main/res/layout/fragment_internet_activity_policy.xml +++ b/app/src/main/res/layout/fragment_internet_activity_policy.xml @@ -24,9 +24,7 @@ android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" - android:paddingLeft="32dp" - android:paddingRight="32dp" - tools:context=".main.MainActivity" + android:padding="16dp" > <TextView @@ -34,33 +32,8 @@ android:layout_gravity="center_horizontal" android:layout_height="wrap_content" android:layout_width="match_parent" - android:paddingTop="16dp" - android:text="@string/internet_activity_privacy_info" - android:textColor="@color/black" - android:textSize="14sp" - /> - - <TextView - android:fontFamily="sans-serif-medium" - android:gravity="center_vertical" - android:id="@+id/learn_more_internet_activity_privacy_info" - android:layout_height="48dp" - android:layout_width="wrap_content" - android:text="@string/learn_more" - android:textColor="#007fff" - android:textSize="14sp" - /> - - <TextView - android:fontFamily="sans-serif-medium" - android:id="@+id/my_internet_activity_header" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:paddingBottom="8dp" - android:paddingTop="16dp" - android:text="@string/dashboard_internet_activity_privacy_title" - android:textColor="@color/black" - android:textSize="14sp" + android:text="@string/ipscrambling_info" + android:lineSpacingExtra="5sp" /> <RadioGroup @@ -69,114 +42,82 @@ android:layout_width="match_parent" android:orientation="vertical" > - - <foundation.e.privacycentralapp.common.RightRadioButton + <include layout="@layout/ipscrambling_item_selectmode" + android:id="@+id/radio_use_hidden_ip" + android:layout_width="match_parent" + android:layout_height="88dp" + app:title="@{@string/ipscrambling_hide_ip_label}" + app:subtitle="@{@string/ipscrambling_hide_ip_subtitle}" + /> + <include layout="@layout/ipscrambling_item_selectmode" android:id="@+id/radio_use_real_ip" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:text="@string/use_real_ip" - android:textSize="16sp" - /> - - <TextView - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:text="@string/i_can_be_tracked" - android:textSize="14sp" - /> - - <foundation.e.privacycentralapp.common.RightRadioButton - android:id="@+id/radio_use_hidden_ip" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" android:layout_width="match_parent" - android:text="@string/hidden_ip" - android:textSize="16sp" - /> - - <TextView - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:text="@string/i_am_anonymous" - android:textSize="14sp" + android:layout_height="88dp" + app:title="@{@string/ipscrambling_real_ip_label}" + app:subtitle="@{@string/ipscrambling_real_ip_subtitle}" /> </RadioGroup> - <TextView android:id="@+id/ipscrambling_tor_status" + + <TextView android:id="@+id/tor_disclaimer" android:layout_height="wrap_content" android:layout_width="match_parent" - android:paddingTop="24dp" - android:text="@string/ipscrambling_is_starting" - android:textColor="@color/black" - android:textSize="16sp" - android:visibility="gone" + android:padding="4dp" + android:textSize="12sp" + android:textColor="@color/black_text_2" + android:text="@string/ipscrambling_tor_disclaimer" + /> <ProgressBar - android:id="@+id/ipscrambling_loading" + android:id="@+id/loader" android:layout_width="48dp" android:layout_height="48dp" android:layout_margin="24dp" android:layout_gravity="center" + android:visibility="gone" /> - <LinearLayout - android:id="@+id/ipscrambling_location" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:paddingTop="32dp" - > - <TextView - android:id="@+id/ipscrambling_location_label" - android:layout_height="wrap_content" - android:layout_width="wrap_content" android:text="@string/ipscrambling_location_label" - android:textColor="@color/black" - android:textSize="16sp" - /> - <Spinner android:id="@+id/ipscrambling_select_location" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - /> - </LinearLayout> - <TextView - android:id="@+id/ipscrambling_activated" + android:id="@+id/ipscrambling_location_label" android:layout_height="wrap_content" android:layout_width="match_parent" - android:paddingTop="32dp" - android:text="@string/ipscrambling_all_apps_scrambled" - android:textColor="@color/black" - android:paddingBottom="8dp" - android:textSize="16sp" - android:visibility="gone" + android:layout_marginTop="16dp" + android:text="@string/ipscrambling_location_label" + android:textColor="@color/black_text" + android:textFontWeight="500" + android:textSize="14sp" /> - </LinearLayout> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/recycler_view_scrambled" + <FrameLayout + android:id="@+id/select_location_container" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" - /> + android:layout_height="54dp" + android:background="@drawable/outlined_background" + android:layout_marginTop="16dp" + android:padding="1dp" + > + <Spinner android:id="@+id/ipscrambling_select_location" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + /> + </FrameLayout> <TextView android:id="@+id/ipscrambling_select_apps" android:layout_height="wrap_content" android:layout_width="match_parent" - android:paddingStart="32dp" - android:paddingEnd="32dp" - android:paddingTop="32dp" - android:paddingBottom="8dp" + android:layout_marginTop="32dp" + android:layout_marginBottom="16dp" android:text="@string/ipscrambling_select_app" - android:textColor="@color/black" - android:textSize="16sp" - android:visibility="gone" + android:textColor="@color/black_text" + android:textFontWeight="500" + android:textSize="14sp" /> + </LinearLayout> + <androidx.recyclerview.widget.RecyclerView - android:id="@+id/recycler_view_to_select" + android:id="@+id/apps" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="gone" /> </LinearLayout> </androidx.core.widget.NestedScrollView> diff --git a/app/src/main/res/layout/ipscrambling_item_app_toggle.xml b/app/src/main/res/layout/ipscrambling_item_app_toggle.xml new file mode 100644 index 0000000..117b834 --- /dev/null +++ b/app/src/main/res/layout/ipscrambling_item_app_toggle.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.appcompat.widget.LinearLayoutCompat + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_height="52dp" + android:layout_width="match_parent" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:gravity="center_vertical" + > + + <ImageView + android:id="@+id/icon" + android:layout_height="32dp" + android:layout_width="32dp" + android:src="@drawable/ic_facebook" + /> + + <TextView + android:id="@+id/title" + android:layout_centerVertical="true" + android:layout_height="wrap_content" + android:layout_width="0dp" + android:layout_weight="1" + android:maxLines="1" + android:ellipsize="end" + android:layout_marginStart="16dp" + android:textSize="14sp" + tools:text="Body sensor" + /> + + <CheckBox + android:id="@+id/toggle" + android:layout_width="wrap_content" + android:layout_height="24dp" + android:layoutDirection="rtl" + android:checked="true" + /> +</androidx.appcompat.widget.LinearLayoutCompat> diff --git a/app/src/main/res/layout/ipscrambling_item_selectmode.xml b/app/src/main/res/layout/ipscrambling_item_selectmode.xml new file mode 100644 index 0000000..4758d56 --- /dev/null +++ b/app/src/main/res/layout/ipscrambling_item_selectmode.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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/>. + --> + +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + > + <data> + <variable name="title" type="String"/> + <variable name="subtitle" type="String"/> + </data> + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="88dp" + > + <foundation.e.privacycentralapp.common.RightRadioButton + android:id="@+id/radiobutton" + android:layout_height="match_parent" + android:layout_width="match_parent" + /> + <TextView + android:id="@+id/title" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@{title}" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@+id/subtitle" + app:layout_constraintVertical_chainStyle="packed" + /> + <TextView + android:id="@+id/subtitle" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@{subtitle}" + android:textSize="14sp" + android:textColor="@color/grey_text" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@+id/title" + app:layout_constraintBottom_toBottomOf="parent" + /> + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 2a16240..5f4bcb4 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -11,6 +11,7 @@ <color name="accent">@lineageos.platform:color/color_default_accent</color> <color name="black_text">#DE000000</color> + <color name="black_text_2">#BD000000</color> <color name="grey_text">#99000000</color> <color name="grey_text_2">#61000000</color> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f6bcf72..35987ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,8 +30,20 @@ <string name="dashboard_internet_activity_privacy_subtitle_off">Real IP address exposed</string> <string name="dashboard_internet_activity_privacy_subtitle_on">Real IP address hidden</string> + <!-- IpScrambling --> + <string name="ipscrambling_title">My internet activity</string> + <string name="ipscrambling_info">Choose if you want to expose your real IP address or hide when Quick Privacy is enabled.</string> + <string name="ipscrambling_real_ip_label">Use real IP address</string> + <string name="ipscrambling_real_ip_subtitle">I can be tracked by my IP address</string> + <string name="ipscrambling_hide_ip_label">Hide IP address</string> + <string name="ipscrambling_hide_ip_subtitle">I am anonymous on the internet</string> + <string name="ipscrambling_tor_disclaimer"><b>Note:</b> when active, this setting will slow down your Internet connectivity speed (uses Thor network).</string> + <string name="ipscrambling_location_label">Force a country of origin:</string> + <string name="ipscrambling_any_location">Any country</string> + + <string name="ipscrambling_select_app">Apply this setting to all selected apps:</string> + <!-- --> - <string name="internet_activity_privacy">Internet Activity Privacy</string> <string name="quick_protection_info">Quick protection enables these settings when turned on</string> <string name="quick_protection_settings_list"> - All trackers are turned off.\n- Your geolocation will be faked.\n- Your real IP address will be hidden.</string> <string name="learn_more">Learn more</string> @@ -42,19 +54,12 @@ <string name="longitude">Longitude</string> <string name="latitude">Latitude</string> <string name="add_location">Add location</string> - <string name="internet_activity_privacy_info">Choose if you want to expose your real IP address or hide when connected to the internet (uses the tor network).</string> - <string name="use_real_ip">Use real IP address</string> - <string name="i_can_be_tracked">I can be tracked by my IP address</string> - <string name="hidden_ip">Hide IP address</string> - <string name="i_am_anonymous">I am anonymous on the internet</string> <string name="i_am_exposing">I am exposing my real IP address</string> <string name="ipscrambling_all_apps_scrambled">All apps use hidden IP</string> <string name="ipscrambling_only_selected_apps_scrambled">Only the following apps use hidden IP</string> - <string name="ipscrambling_select_app">Select Apps to hide IP</string> + <string name="ipscrambling_is_starting">Tor is starting...</string> <string name="ipscrambling_is_stopping">Tor is stopping...</string> - <string name="ipscrambling_location_label">Hidden IP\'s location</string> - <string name="ipscrambling_any_location">any</string> ipscrambling_any_location <string name="permission_control_info">Manage and control apps requesting various permissions.</string> |