summaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
authorAmit Kumar <amitkma@e.email>2021-05-04 01:26:13 +0530
committerAmit Kumar <amitkma@e.email>2021-05-04 01:26:13 +0530
commit076144023e449d83b64363dc0da0fa00a0ea2c00 (patch)
tree05fee3ff7ac178d4277ff8da7b4a2e78962b33eb /app/src/main
parent6f9180d527cecf5c65d0aac5f75ecfefe1ff5a9e (diff)
Add permission feature and improve code quality
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt72
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt17
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt86
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt2
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt2
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt2
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsAdapter.kt18
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsFragment.kt105
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionControlFragment.kt59
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFeature.kt118
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFragment.kt55
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsViewModel.kt40
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt3
-rw-r--r--app/src/main/res/layout/fragment_permission_apps.xml (renamed from app/src/main/res/layout/fragment_permission_control.xml)0
-rw-r--r--app/src/main/res/values/strings.xml9
15 files changed, 434 insertions, 154 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt
index 3f2dc1e..aef994b 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt
@@ -45,10 +45,11 @@ import kotlin.random.Random
* Dummmy permission data class.
*/
data class Permission(
+ val id: Int,
val name: String,
val iconId: Int,
- val packagesRequested: List<String> = emptyList(),
- val packagesAllowed: List<String> = emptyList()
+ val packagesRequested: Set<String> = emptySet(),
+ val packagesAllowed: Set<String> = emptySet()
)
enum class LocationMode {
@@ -62,8 +63,6 @@ enum class InternetPrivacyMode {
data class Location(val mode: LocationMode, val latitude: Double, val longitude: Double)
object DummyDataSource {
- private val _appsUsingLocationPerm = MutableStateFlow<List<String>>(emptyList())
- val appsUsingLocationPerm = _appsUsingLocationPerm.asStateFlow()
const val trackersCount = 77
private val _activeTrackersCount = MutableStateFlow(10)
@@ -75,8 +74,17 @@ object DummyDataSource {
private val _internetActivityMode = MutableStateFlow(InternetPrivacyMode.REAL_IP)
val internetActivityMode = _internetActivityMode.asStateFlow()
+ /**
+ * Declare dummy permissions with following ids
+ *
+ * [0] -> Body sensor
+ * [1] -> Calendar
+ * [2] -> Call Logs
+ * [3] -> Location
+ */
val permissions = arrayOf("Body Sensor", "Calendar", "Call Logs", "Location")
- val icons = arrayOf(
+
+ private val permissionIcons = arrayOf(
R.drawable.ic_body_monitor,
R.drawable.ic_calendar,
R.drawable.ic_call,
@@ -98,22 +106,26 @@ object DummyDataSource {
"privacycentral"
)
- val populatedPermission: List<Permission> by lazy {
- fetchPermissions()
- }
+ val _populatedPermissions = MutableStateFlow(fetchPermissions())
+ val populatedPermission = _populatedPermissions.asStateFlow()
+
+ private val _appsUsingLocationPerm =
+ MutableStateFlow(_populatedPermissions.value[3].packagesAllowed)
+ val appsUsingLocationPerm = _appsUsingLocationPerm.asStateFlow()
private fun fetchPermissions(): List<Permission> {
val result = mutableListOf<Permission>()
permissions.forEachIndexed { index, permission ->
when (index) {
- 0 -> result.add(Permission(permission, icons[index]))
+ 0 -> result.add(Permission(index, permission, permissionIcons[index]))
1 -> {
val randomPackages = getRandomItems(packages, 8)
val grantedPackages = getRandomItems(randomPackages, 3)
result.add(
Permission(
+ index,
permission,
- icons[index],
+ permissionIcons[index],
randomPackages,
grantedPackages
)
@@ -124,8 +136,9 @@ object DummyDataSource {
val grantedPackages = getRandomItems(randomPackages, 9)
result.add(
Permission(
+ index,
permission,
- icons[index],
+ permissionIcons[index],
randomPackages,
grantedPackages
)
@@ -136,8 +149,9 @@ object DummyDataSource {
val grantedPackages = getRandomItems(randomPackages, 3)
result.add(
Permission(
+ index,
permission,
- icons[index],
+ permissionIcons[index],
randomPackages,
grantedPackages
)
@@ -148,10 +162,10 @@ object DummyDataSource {
return result
}
- private fun <T> getRandomItems(data: Array<T>, limit: Int): List<T> =
- getRandomItems(data.asList(), limit)
+ private fun <T> getRandomItems(data: Array<T>, limit: Int): Set<T> =
+ getRandomItems(data.toSet(), limit)
- private fun <T> getRandomItems(data: List<T>, limit: Int): List<T> {
+ private fun <T> getRandomItems(data: Set<T>, limit: Int): Set<T> {
val randomItems = mutableSetOf<T>()
val localData = data.toMutableList()
repeat(limit) {
@@ -159,12 +173,12 @@ object DummyDataSource {
randomItems.add(generated)
localData.remove(generated)
}
- return randomItems.toList()
+ return randomItems
}
- fun getPermission(permissionId: Int): Permission {
- return populatedPermission.get(permissionId)
- }
+ fun getPermission(permissionId: Int): Permission = populatedPermission.value[permissionId]
+
+ fun getLocationPermissionApps(): Permission = getPermission(3)
fun setLocationMode(locationMode: LocationMode, location: Location? = null): Boolean {
when (locationMode) {
@@ -190,4 +204,24 @@ object DummyDataSource {
_internetActivityMode.value = mode
return true
}
+
+ fun togglePermission(permissionId: Int, packageName: String, grant: Boolean) {
+ val allPermissions = _populatedPermissions.value.toMutableList()
+ val permission: Permission = allPermissions[permissionId].let { permission ->
+
+ val packagesAllowed = permission.packagesAllowed.toMutableSet()
+
+ if (grant) packagesAllowed.add(packageName)
+ else packagesAllowed.remove(packageName)
+
+ permission.copy(packagesAllowed = packagesAllowed)
+ }
+ allPermissions[permissionId] = permission
+ _populatedPermissions.value = allPermissions
+
+ // Update when permission is toggled for Location
+ if (permissionId == 3) {
+ _appsUsingLocationPerm.value = _populatedPermissions.value[permissionId].packagesAllowed
+ }
+ }
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt
index c872012..133ad84 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt
@@ -17,13 +17,16 @@
package foundation.e.privacycentralapp.dummy
-fun LocationMode.mapToString(): String = when (this) {
- LocationMode.REAL_LOCATION -> "Real location mode"
- LocationMode.RANDOM_LOCATION -> "Random location mode"
- LocationMode.CUSTOM_LOCATION -> "Fake location mode"
+import android.content.Context
+import foundation.e.privacycentralapp.R
+
+fun LocationMode.mapToString(context: Context): String = when (this) {
+ LocationMode.REAL_LOCATION -> context.getString(R.string.real_location_mode)
+ LocationMode.RANDOM_LOCATION -> context.getString(R.string.random_location_mode)
+ LocationMode.CUSTOM_LOCATION -> context.getString(R.string.fake_location_mode)
}
-fun InternetPrivacyMode.mapToString(): String = when (this) {
- InternetPrivacyMode.REAL_IP -> "I'm exposing my real IP address"
- InternetPrivacyMode.HIDE_IP -> "I'm anonymous on the internet"
+fun InternetPrivacyMode.mapToString(context: Context): String = when (this) {
+ InternetPrivacyMode.REAL_IP -> context.getString(R.string.i_am_exposing)
+ InternetPrivacyMode.HIDE_IP -> context.getString(R.string.i_am_anonymous)
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt
index b9371be..f0a7397 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt
@@ -55,29 +55,36 @@ class DashboardFragment :
}
lifecycleScope.launchWhenStarted {
viewModel.dashboardFeature.singleEvents.collect { event ->
- if (event is DashboardFeature.SingleEvent.NavigateToLocationSingleEvent) {
- requireActivity().supportFragmentManager.commit {
- add<FakeLocationFragment>(R.id.container)
- setReorderingAllowed(true)
- addToBackStack("dashboard")
+ when (event) {
+ is DashboardFeature.SingleEvent.NavigateToLocationSingleEvent -> {
+ requireActivity().supportFragmentManager.commit {
+ add<FakeLocationFragment>(R.id.container)
+ setReorderingAllowed(true)
+ addToBackStack("dashboard")
+ }
}
- } else if (event is DashboardFeature.SingleEvent.NavigateToQuickProtectionSingleEvent) {
- requireActivity().supportFragmentManager.commit {
- add<QuickProtectionFragment>(R.id.container)
- setReorderingAllowed(true)
- addToBackStack("dashboard")
+ is DashboardFeature.SingleEvent.NavigateToQuickProtectionSingleEvent -> {
+ requireActivity().supportFragmentManager.commit {
+ add<QuickProtectionFragment>(R.id.container)
+ setReorderingAllowed(true)
+ addToBackStack("dashboard")
+ }
}
- } else if (event is DashboardFeature.SingleEvent.NavigateToInternetActivityPrivacySingleEvent) {
- requireActivity().supportFragmentManager.commit {
- add<InternetPrivacyFragment>(R.id.container)
- setReorderingAllowed(true)
- addToBackStack("dashboard")
+ is DashboardFeature.SingleEvent.NavigateToInternetActivityPrivacySingleEvent -> {
+ requireActivity().supportFragmentManager.commit {
+ add<InternetPrivacyFragment>(R.id.container)
+ setReorderingAllowed(true)
+ addToBackStack("dashboard")
+ }
}
- } else if (event is DashboardFeature.SingleEvent.NavigateToPermissionsSingleEvent) {
- requireActivity().supportFragmentManager.commit {
- add<PermissionsFragment>(R.id.container)
- setReorderingAllowed(true)
- addToBackStack("dashboard")
+ is DashboardFeature.SingleEvent.NavigateToPermissionsSingleEvent -> {
+ requireActivity().supportFragmentManager.commit {
+ add<PermissionsFragment>(R.id.container)
+ setReorderingAllowed(true)
+ addToBackStack("dashboard")
+ }
+ }
+ DashboardFeature.SingleEvent.NavigateToTrackersSingleEvent -> {
}
}
}
@@ -149,27 +156,13 @@ class DashboardFragment :
state.totalApps,
state.permissionCount
)
- view.findViewById<TextView>(R.id.my_location_subtitle).let {
- it.text = getString(
+ view.findViewById<TextView>(R.id.my_location_subtitle).let { textView ->
+ textView.text = getString(
R.string.my_location_subtitle,
state.appsUsingLocationPerm,
)
- it.append(
- SpannableString(state.locationMode.mapToString())
- .also {
- it.setSpan(
- ForegroundColorSpan(Color.parseColor("#007fff")),
- 0,
- it.length,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- }
- )
- }
- view.findViewById<TextView>(R.id.internet_activity_privacy_subtitle).let {
- it.text = getString(R.string.internet_activity_privacy_subtitle)
- it.append(
- SpannableString(state.internetPrivacyMode.mapToString())
+ textView.append(
+ SpannableString(state.locationMode.mapToString(requireContext()))
.also {
it.setSpan(
ForegroundColorSpan(Color.parseColor("#007fff")),
@@ -180,8 +173,25 @@ class DashboardFragment :
}
)
}
+ view.findViewById<TextView>(R.id.internet_activity_privacy_subtitle)
+ .let { textView ->
+ textView.text = getString(R.string.internet_activity_privacy_subtitle)
+ textView.append(
+ SpannableString(state.internetPrivacyMode.mapToString(requireContext()))
+ .also {
+ it.setSpan(
+ ForegroundColorSpan(Color.parseColor("#007fff")),
+ 0,
+ it.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ }
+ )
+ }
}
}
+ DashboardFeature.State.QuickProtectionState -> {
+ }
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
index 9428f41..1a3b3df 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
@@ -17,7 +17,6 @@
package foundation.e.privacycentralapp.features.dashboard
-import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -34,7 +33,6 @@ class DashboardViewModel : ViewModel() {
}
fun submitAction(action: DashboardFeature.Action) {
- Log.d("DashboardViewModel", "submitAction() called with: action = $action")
viewModelScope.launch {
_actions.emit(action)
}
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 fc4cf97..b34024e 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
@@ -40,7 +40,7 @@ class InternetPrivacyFeature(
actor,
reducer,
coroutineScope,
- { message -> Log.d("FakeLocationFeature", message) },
+ { message -> Log.d("InternetPrivacyFeature", message) },
singleEventProducer
) {
data class State(val mode: InternetPrivacyMode)
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
index 6831680..5b58293 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
@@ -169,6 +169,8 @@ class FakeLocationFragment :
}
}
}
+ FakeLocationFeature.State.InitialState -> {
+ }
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsAdapter.kt
index 19460cc..4f9b602 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsAdapter.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsAdapter.kt
@@ -21,25 +21,33 @@ import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.ImageView
import android.widget.Switch
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import foundation.e.privacycentralapp.R
-class PermissionAppsAdapter(private val dataSet: List<Pair<String, Boolean>>) :
+class PermissionAppsAdapter(
+ private val dataSet: List<Pair<String, Boolean>>,
+ private val listener: (String, Boolean) -> Unit
+) :
RecyclerView.Adapter<PermissionAppsAdapter.PermissionViewHolder>() {
class PermissionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val appName: TextView = view.findViewById(R.id.app_title)
- @SuppressLint("UseSwitchCompatOrMaterialCode") val togglePermission: Switch = view.findViewById(R.id.togglePermission)
- val appIcon: ImageView = view.findViewById(R.id.app_icon)
+
+ @SuppressLint("UseSwitchCompatOrMaterialCode")
+ val togglePermission: Switch = view.findViewById(R.id.togglePermission)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_permission_apps, parent, false)
- return PermissionViewHolder(view)
+ val holder = PermissionViewHolder(view)
+ holder.togglePermission.setOnCheckedChangeListener { _, isChecked ->
+ listener(dataSet[holder.adapterPosition].first, isChecked)
+ }
+ view.findViewById<Switch>(R.id.togglePermission)
+ return holder
}
override fun onBindViewHolder(holder: PermissionViewHolder, position: Int) {
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsFragment.kt
new file mode 100644
index 0000000..224d1be
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsFragment.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.features.permissions
+
+import android.os.Bundle
+import android.view.View
+import android.widget.TextView
+import android.widget.Toast
+import android.widget.Toolbar
+import androidx.fragment.app.Fragment
+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.R
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+
+class PermissionAppsFragment :
+ Fragment(R.layout.fragment_permission_apps),
+ MVIView<PermissionsFeature.State, PermissionsFeature.Action> {
+
+ private val viewModel: PermissionsViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ lifecycleScope.launchWhenStarted {
+ viewModel.permissionsFeature.takeView(this, this@PermissionAppsFragment)
+ }
+ lifecycleScope.launchWhenStarted {
+ viewModel.permissionsFeature.singleEvents.collect { event ->
+ when (event) {
+ is PermissionsFeature.SingleEvent.ErrorEvent -> displayToast(event.error)
+ }
+ }
+ }
+ lifecycleScope.launchWhenStarted {
+ viewModel.submitAction(
+ PermissionsFeature.Action.LoadPermissionApps(
+ requireArguments().getInt(
+ "PERMISSION_ID"
+ )
+ )
+ )
+ }
+ }
+
+ private fun displayToast(message: String) {
+ Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT)
+ .show()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val toolbar = view.findViewById<Toolbar>(R.id.toolbar)
+ setupToolbar(toolbar)
+ }
+
+ private fun setupToolbar(toolbar: Toolbar) {
+ val activity = requireActivity()
+ activity.setActionBar(toolbar)
+ activity.title = "My Apps Permission"
+ }
+
+ override fun render(state: PermissionsFeature.State) {
+ state.currentPermission?.let { permission ->
+ view?.findViewById<RecyclerView>(R.id.recylcer_view_permission_apps)?.apply {
+ val listOfPackages = mutableListOf<Pair<String, Boolean>>()
+ permission.packagesRequested.forEach {
+ listOfPackages.add(it to permission.packagesAllowed.contains(it))
+ }
+ layoutManager = LinearLayoutManager(requireContext())
+ setHasFixedSize(true)
+ adapter = PermissionAppsAdapter(listOfPackages) { packageName, grant ->
+ viewModel.submitAction(
+ PermissionsFeature.Action.TogglePermissionAction(
+ packageName,
+ grant
+ )
+ )
+ }
+ }
+ view?.findViewById<TextView>(R.id.permission_control)?.text =
+ getString(R.string.apps_access_to_permission, permission.name)
+ }
+ }
+
+ override fun actions(): Flow<PermissionsFeature.Action> = viewModel.actions
+}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionControlFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionControlFragment.kt
deleted file mode 100644
index 55e3f88..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionControlFragment.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.features.permissions
-
-import android.os.Bundle
-import android.view.View
-import android.widget.TextView
-import android.widget.Toolbar
-import androidx.fragment.app.Fragment
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import foundation.e.privacycentralapp.R
-import foundation.e.privacycentralapp.dummy.DummyDataSource
-
-class PermissionControlFragment : Fragment(R.layout.fragment_permission_control) {
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- val toolbar = view.findViewById<Toolbar>(R.id.toolbar)
- setupToolbar(toolbar)
-
- val permissionId = requireArguments().getInt("PERMISSION_ID")
- loadData(view, permissionId)
- }
-
- private fun loadData(view: View, permissionId: Int) {
- val recyclerView = view.findViewById<RecyclerView>(R.id.recylcer_view_permission_apps)
- val permission = DummyDataSource.getPermission(permissionId)
- val listOfPackages = mutableListOf<Pair<String, Boolean>>()
- permission.packagesRequested.forEach {
- listOfPackages.add(it to permission.packagesAllowed.contains(it))
- }
- recyclerView.layoutManager = LinearLayoutManager(requireContext())
- recyclerView.setHasFixedSize(true)
- recyclerView.adapter = PermissionAppsAdapter(listOfPackages)
- view.findViewById<TextView>(R.id.permission_control).text =
- getString(R.string.apps_access_to_permission, permission.name)
- }
-
- private fun setupToolbar(toolbar: Toolbar) {
- val activity = requireActivity()
- activity.setActionBar(toolbar)
- activity.title = "My Apps Permission"
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFeature.kt
new file mode 100644
index 0000000..d095f00
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFeature.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.features.permissions
+
+import android.util.Log
+import foundation.e.flowmvi.Actor
+import foundation.e.flowmvi.Reducer
+import foundation.e.flowmvi.SingleEventProducer
+import foundation.e.flowmvi.feature.BaseFeature
+import foundation.e.privacycentralapp.dummy.DummyDataSource
+import foundation.e.privacycentralapp.dummy.Permission
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+// Define a state machine for Internet privacy feature
+class PermissionsFeature(
+ initialState: State,
+ coroutineScope: CoroutineScope,
+ reducer: Reducer<State, Effect>,
+ actor: Actor<State, Action, Effect>,
+ singleEventProducer: SingleEventProducer<State, Action, Effect, SingleEvent>
+) : BaseFeature<PermissionsFeature.State, PermissionsFeature.Action, PermissionsFeature.Effect, PermissionsFeature.SingleEvent>(
+ initialState,
+ actor,
+ reducer,
+ coroutineScope,
+ { message -> Log.d("PermissionsFeature", message) },
+ singleEventProducer
+) {
+ data class State(
+ val permissions: List<Permission> = emptyList(),
+ val currentPermission: Permission? = null
+ )
+
+ sealed class SingleEvent {
+ data class ErrorEvent(val error: String) : SingleEvent()
+ }
+
+ sealed class Action {
+ object ObservePermissions : Action()
+ data class LoadPermissionApps(val id: Int) : Action()
+ data class TogglePermissionAction(
+ val packageName: String,
+ val grant: Boolean
+ ) : Action()
+ }
+
+ sealed class Effect {
+ data class PermissionsLoadedEffect(val permissions: List<Permission>) : Effect()
+ data class PermissionLoadedEffect(val permission: Permission) : Effect()
+ object PermissionToggledEffect : Effect()
+ data class ErrorEffect(val message: String) : Effect()
+ }
+
+ companion object {
+ fun create(
+ initialState: State = State(),
+ coroutineScope: CoroutineScope
+ ) = PermissionsFeature(
+ initialState, coroutineScope,
+ reducer = { state, effect ->
+ when (effect) {
+ is Effect.PermissionsLoadedEffect -> State(effect.permissions)
+ is Effect.PermissionLoadedEffect -> state.copy(currentPermission = effect.permission)
+ is Effect.ErrorEffect -> state
+ Effect.PermissionToggledEffect -> state
+ }
+ },
+ actor = { state, action ->
+ when (action) {
+ Action.ObservePermissions -> DummyDataSource.populatedPermission.map {
+ Effect.PermissionsLoadedEffect(it)
+ }
+ is Action.LoadPermissionApps -> flowOf(
+ Effect.PermissionLoadedEffect(
+ DummyDataSource.getPermission(action.id)
+ )
+ )
+
+ is Action.TogglePermissionAction -> {
+ if (state.currentPermission != null) {
+ DummyDataSource.togglePermission(
+ state.currentPermission.id,
+ action.packageName,
+ action.grant
+ )
+ flowOf(Effect.PermissionToggledEffect)
+ } else {
+ flowOf(Effect.ErrorEffect("Can't update permission"))
+ }
+ }
+ }
+ },
+ singleEventProducer = { _, _, effect ->
+ when (effect) {
+ is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message)
+ else -> null
+ }
+ }
+ )
+ }
+}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFragment.kt
index 27247ea..864a355 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFragment.kt
@@ -24,32 +24,34 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.add
import androidx.fragment.app.commit
+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.R
-import foundation.e.privacycentralapp.dummy.DummyDataSource
+import kotlinx.coroutines.flow.Flow
+
+class PermissionsFragment :
+ Fragment(R.layout.fragment_permissions),
+ MVIView<PermissionsFeature.State, PermissionsFeature.Action> {
+
+ private val viewModel: PermissionsViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ lifecycleScope.launchWhenStarted {
+ viewModel.permissionsFeature.takeView(this, this@PermissionsFragment)
+ }
+ lifecycleScope.launchWhenStarted {
+ viewModel.submitAction(PermissionsFeature.Action.ObservePermissions)
+ }
+ }
-class PermissionsFragment : Fragment(R.layout.fragment_permissions) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val toolbar = view.findViewById<Toolbar>(R.id.toolbar)
setupToolbar(toolbar)
-
- loadDataIntoRecyclerView(view.findViewById(R.id.recylcer_view_permissions))
- }
-
- private fun loadDataIntoRecyclerView(view: RecyclerView) {
- val permissions = DummyDataSource.populatedPermission
- view.layoutManager = LinearLayoutManager(requireContext())
- view.setHasFixedSize(true)
- view.adapter = PermissionsAdapter(requireContext(), permissions) { permissionId ->
- requireActivity().supportFragmentManager.commit {
- val bundle = bundleOf("PERMISSION_ID" to permissionId)
- add<PermissionControlFragment>(R.id.container, args = bundle)
- setReorderingAllowed(true)
- addToBackStack("permissions")
- }
- }
}
private fun setupToolbar(toolbar: Toolbar) {
@@ -57,4 +59,21 @@ class PermissionsFragment : Fragment(R.layout.fragment_permissions) {
activity.setActionBar(toolbar)
activity.title = "My Apps Permission"
}
+
+ override fun render(state: PermissionsFeature.State) {
+ view?.findViewById<RecyclerView>(R.id.recylcer_view_permissions)?.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ setHasFixedSize(true)
+ adapter = PermissionsAdapter(requireContext(), state.permissions) { permissionId ->
+ requireActivity().supportFragmentManager.commit {
+ val bundle = bundleOf("PERMISSION_ID" to permissionId)
+ add<PermissionAppsFragment>(R.id.container, args = bundle)
+ setReorderingAllowed(true)
+ addToBackStack("permissions")
+ }
+ }
+ }
+ }
+
+ override fun actions(): Flow<PermissionsFeature.Action> = viewModel.actions
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsViewModel.kt
new file mode 100644
index 0000000..fc50c39
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.features.permissions
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
+
+class PermissionsViewModel : ViewModel() {
+
+ private val _actions = MutableSharedFlow<PermissionsFeature.Action>()
+ val actions = _actions.asSharedFlow()
+
+ val permissionsFeature: PermissionsFeature by lazy {
+ PermissionsFeature.create(coroutineScope = viewModelScope)
+ }
+
+ fun submitAction(action: PermissionsFeature.Action) {
+ viewModelScope.launch {
+ _actions.emit(action)
+ }
+ }
+}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt
index 42f9e24..1b92cb2 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt
@@ -20,7 +20,6 @@ package foundation.e.privacycentralapp.main
import android.app.Activity
import android.content.Intent
import android.os.Bundle
-import androidx.activity.viewModels
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.add
import androidx.fragment.app.commit
@@ -29,8 +28,6 @@ import foundation.e.privacycentralapp.features.dashboard.DashboardFragment
open class MainActivity : FragmentActivity(R.layout.activity_main) {
- private val viewModel: MainViewModel by viewModels()
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
diff --git a/app/src/main/res/layout/fragment_permission_control.xml b/app/src/main/res/layout/fragment_permission_apps.xml
index 2888af0..2888af0 100644
--- a/app/src/main/res/layout/fragment_permission_control.xml
+++ b/app/src/main/res/layout/fragment_permission_apps.xml
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 989e233..ff0cf0a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -23,11 +23,16 @@
<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="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_anonymous">I am anonymous on the internet</string>
+ <string name="i_am_exposing">I am exposing my real IP address</string>
<string name="permission_control_info">Manage and control apps requesting various permissions.</string>
<string name="apps_allowed">%1$d of %2$d apps allowed</string>
<string name="apps_access_to_permission">Apps which has access to %1$s permission</string>
+
+ <string name="real_location_mode">Real location mode</string>
+ <string name="random_location_mode">Random location mode</string>
+ <string name="fake_location_mode">Fake location mode</string>
</resources> \ No newline at end of file