summaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt11
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/UpdateTrackersWorker.kt53
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt17
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt37
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt23
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt50
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt8
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt23
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt57
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt6
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt12
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt24
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt31
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt29
14 files changed, 282 insertions, 99 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
index 76a9539..639e7b4 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
@@ -50,8 +50,7 @@ import kotlinx.coroutines.GlobalScope
*
* TODO: Test if this implementation is leaky.
*/
-class DependencyContainer constructor(val app: Application) {
-
+class DependencyContainer(val app: Application) {
val context: Context by lazy { app.applicationContext }
// Drivers
@@ -89,10 +88,10 @@ class DependencyContainer constructor(val app: Application) {
private val appListUseCase = AppListUseCase(appListsRepository)
private val trackersStatisticsUseCase by lazy {
- TrackersStatisticsUseCase(trackTrackersPrivacyModule, appListsRepository, context.resources)
+ TrackersStatisticsUseCase(trackTrackersPrivacyModule, blockTrackersPrivacyModule, appListsRepository, context.resources)
}
- private val trackersStateUseCase by lazy {
+ val trackersStateUseCase by lazy {
TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, permissionsModule, localStateRepository, trackersRepository, appListsRepository, GlobalScope)
}
@@ -119,7 +118,7 @@ class DependencyContainer constructor(val app: Application) {
}
val trackersViewModelFactory by lazy {
- TrackersViewModelFactory(trackersStatisticsUseCase, appListUseCase)
+ TrackersViewModelFactory(trackersStatisticsUseCase)
}
val appTrackersViewModelFactory by lazy {
@@ -131,5 +130,7 @@ class DependencyContainer constructor(val app: Application) {
trackersStateUseCase
ipScramblingStateUseCase
fakeLocationStateUseCase
+
+ UpdateTrackersWorker.periodicUpdate(context)
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/UpdateTrackersWorker.kt b/app/src/main/java/foundation/e/privacycentralapp/UpdateTrackersWorker.kt
new file mode 100644
index 0000000..ba6bae9
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/UpdateTrackersWorker.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkManager
+import androidx.work.WorkerParameters
+import java.time.temporal.ChronoUnit
+
+class UpdateTrackersWorker(appContext: Context, workerParams: WorkerParameters) :
+ CoroutineWorker(appContext, workerParams) {
+
+ override suspend fun doWork(): Result {
+ val trackersStateUseCase = (applicationContext as PrivacyCentralApplication)
+ .dependencyContainer.trackersStateUseCase
+
+ trackersStateUseCase.updateTrackers()
+ return Result.success()
+ }
+
+ companion object {
+ fun periodicUpdate(context: Context) {
+ val request = PeriodicWorkRequestBuilder<UpdateTrackersWorker>(
+ ChronoUnit.WEEKS.duration,
+ ChronoUnit.DAYS.duration
+ ).build()
+
+ WorkManager.getInstance(context).enqueueUniquePeriodicWork(
+ UpdateTrackersWorker::class.qualifiedName ?: "",
+ ExistingPeriodicWorkPolicy.REPLACE,
+ request
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
index d66ce76..07cf125 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
@@ -24,7 +24,7 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import foundation.e.privacycentralapp.R
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
+import foundation.e.privacycentralapp.domain.entities.AppWithCounts
class AppsAdapter(
private val itemsLayout: Int,
@@ -34,15 +34,20 @@ class AppsAdapter(
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val appName: TextView = view.findViewById(R.id.title)
-
- fun bind(item: ApplicationDescription) {
+ val counts: TextView = view.findViewById(R.id.counts)
+ val icon: ImageView = view.findViewById(R.id.icon)
+ fun bind(item: AppWithCounts) {
appName.text = item.label
-
- itemView.findViewById<ImageView>(R.id.icon).setImageDrawable(item.icon)
+ counts.text = itemView.context.getString(
+ R.string.trackers_app_trackers_counts,
+ item.blockedTrackersCount,
+ item.trackersCount
+ )
+ icon.setImageDrawable(item.icon)
}
}
- var dataSet: List<ApplicationDescription> = emptyList()
+ var dataSet: List<AppWithCounts> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt
index 4718923..3573d4f 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt
@@ -21,6 +21,7 @@ import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.ApplicationDescription
import kotlinx.coroutines.CoroutineScope
@@ -40,8 +41,10 @@ class AppListsRepository(
coroutineScope.launch {
val (visible, hidden) = splitVisibleToHidden(getAppsUsingInternet())
appDescriptions.emit(
- visible.map { it.toApplicationDescription(withIcon = true) }
- to hidden.map { it.toApplicationDescription() }
+ Pair(
+ visible.map { permissionsModule.buildApplicationDescription(it, withIcon = true) },
+ hidden.map { permissionsModule.buildApplicationDescription(it, withIcon = false) },
+ )
)
}
return appDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } }
@@ -50,18 +53,29 @@ class AppListsRepository(
return appDescriptions.value.second
}
+ fun foldForHiddenSystemApp(appUid: Int, appValueGetter: (Int) -> Int): Int {
+ return if (appUid == dummySystemApp.uid) {
+ getHiddenSystemApps().fold(0) { acc, app ->
+ acc + appValueGetter(app.uid)
+ }
+ } else appValueGetter(appUid)
+ }
+
private val pm get() = context.packageManager
private val appDescriptions = MutableStateFlow(
- emptyList<ApplicationDescription>() to emptyList<ApplicationDescription>()
+ Pair(
+ emptyList<ApplicationDescription>(),
+ emptyList<ApplicationDescription>()
+ )
)
private fun getAppsUsingInternet(): List<ApplicationInfo> {
- return pm.getInstalledApplications(0)
- .filter {
- permissionsModule.getPermissions(it.packageName)
- .contains(Manifest.permission.INTERNET)
- }
+ return pm.getInstalledPackages(PackageManager.GET_PERMISSIONS).filter {
+ it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
+ }.map {
+ it.applicationInfo
+ }
}
private fun isNotHiddenSystemApp(app: ApplicationInfo, launcherApps: List<String>): Boolean {
@@ -94,11 +108,4 @@ class AppListsRepository(
acc
}
}
-
- private fun ApplicationInfo.toApplicationDescription(withIcon: Boolean = true) = ApplicationDescription(
- packageName = packageName,
- uid = uid,
- label = pm.getApplicationLabel(this),
- icon = if (withIcon) pm.getApplicationIcon(packageName) else null
- )
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt
index c7efa84..8216a19 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/TrackersRepository.kt
@@ -20,13 +20,11 @@ package foundation.e.privacycentralapp.data.repositories
import android.content.Context
import android.util.Log
import com.google.gson.Gson
-import com.google.gson.annotations.SerializedName
import foundation.e.privacymodules.trackers.Tracker
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import java.io.InputStreamReader
-import java.lang.Exception
class TrackersRepository(private val context: Context) {
@@ -37,6 +35,12 @@ class TrackersRepository(private val context: Context) {
initFromAssets()
}
+ suspend fun update() {
+ val api = ETrackersApi.build()
+ val response = api.trackers()
+ trackers = mapper(response)
+ }
+
private fun initFromAssets() {
try {
val reader = InputStreamReader(context.getAssets().open("e_trackers.json"), "UTF-8")
@@ -65,8 +69,7 @@ class TrackersRepository(private val context: Context) {
id = id!!,
hostnames = hostnames!!.toSet(),
label = name!!,
- description = description,
- website = website,
+ exodusId = exodusId
)
}
}
@@ -75,14 +78,14 @@ interface ETrackersApi {
companion object {
fun build(): ETrackersApi {
val retrofit = Retrofit.Builder()
- .baseUrl("TODO")
+ .baseUrl("https://gitlab.e.foundation/e/apps/tracker-list/-/raw/main/")
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofit.create(ETrackersApi::class.java)
}
}
- @GET("TODO")
+ @GET("list/e_trackers.json")
suspend fun trackers(): ETrackersResponse
data class ETrackersResponse(val trackers: List<ETracker>) {
@@ -90,13 +93,7 @@ interface ETrackersApi {
val id: String?,
val hostnames: List<String>?,
val name: String?,
-
- val description: String?,
- @SerializedName("creation_date") val creationDate: String?,
- @SerializedName("code_signature") val codeSignature: String?,
- @SerializedName("network_signature") val networkSignature: String?,
- val website: String?,
- val categories: List<String>?,
+ val exodusId: String?
)
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt
new file mode 100644
index 0000000..682dfc8
--- /dev/null
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.entities
+
+import android.graphics.drawable.Drawable
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+
+data class AppWithCounts(
+ val packageName: String,
+ val uid: Int,
+ var label: CharSequence?,
+ var icon: Drawable?,
+ val isWhitelisted: Boolean = false,
+ val trackersCount: Int = 0,
+ val whiteListedTrackersCount: Int = 0
+) {
+ constructor(
+ app: ApplicationDescription,
+ isWhitelisted: Boolean,
+ trackersCount: Int,
+ whiteListedTrackersCount: Int
+ ) :
+ this(
+ packageName = app.packageName,
+ uid = app.uid,
+ label = app.label,
+ icon = app.icon,
+ isWhitelisted = isWhitelisted,
+ trackersCount = trackersCount,
+ whiteListedTrackersCount = whiteListedTrackersCount
+ )
+
+ val blockedTrackersCount get() = if (isWhitelisted) 0
+ else trackersCount - whiteListedTrackersCount
+}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt
index 16a1a82..ecf2e7b 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt
@@ -26,6 +26,7 @@ import foundation.e.privacymodules.trackers.IBlockTrackersPrivacyModule
import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule
import foundation.e.privacymodules.trackers.Tracker
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
@@ -38,7 +39,7 @@ class TrackersStateUseCase(
private val localStateRepository: LocalStateRepository,
private val trackersRepository: TrackersRepository,
private val appListsRepository: AppListsRepository,
- coroutineScope: CoroutineScope
+ private val coroutineScope: CoroutineScope
) {
private val _areAllTrackersBlocked = MutableStateFlow(
@@ -106,4 +107,9 @@ class TrackersStateUseCase(
updateAllTrackersBlockedState()
}
+
+ fun updateTrackers() = coroutineScope.launch {
+ trackersRepository.update()
+ trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false)
+ }
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt
index 9a8b12a..ad8f565 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt
@@ -20,18 +20,22 @@ package foundation.e.privacycentralapp.domain.usecases
import android.content.res.Resources
import foundation.e.privacycentralapp.R
import foundation.e.privacycentralapp.data.repositories.AppListsRepository
+import foundation.e.privacycentralapp.domain.entities.AppWithCounts
import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics
+import foundation.e.privacymodules.trackers.IBlockTrackersPrivacyModule
import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule
import foundation.e.privacymodules.trackers.Tracker
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.map
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
class TrackersStatisticsUseCase(
private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule,
+ private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule,
private val appListsRepository: AppListsRepository,
private val resources: Resources
) {
@@ -124,4 +128,23 @@ class TrackersStatisticsUseCase(
return trackers.sortedBy { it.label.lowercase() }
}
+
+ fun getAppsWithCounts(): Flow<List<AppWithCounts>> {
+ val trackersCounts = trackTrackersPrivacyModule.getTrackersCountByApp()
+ return appListsRepository.getVisibleApps()
+ .map { apps ->
+ apps.map { app ->
+ AppWithCounts(
+ app,
+ blockTrackersPrivacyModule.isWhitelisted(app.uid),
+ appListsRepository.foldForHiddenSystemApp(app.uid) {
+ trackersCounts.getOrDefault(it, 0)
+ },
+ appListsRepository.foldForHiddenSystemApp(app.uid) {
+ blockTrackersPrivacyModule.getWhiteList(it).size
+ }
+ )
+ }
+ }
+ }
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt
index e2eb58d..a606e49 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt
@@ -22,11 +22,9 @@ 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.domain.entities.AppWithCounts
import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics
-import foundation.e.privacycentralapp.domain.usecases.AppListUseCase
import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
-import foundation.e.privacymodules.permissions.data.ApplicationDescription
-import foundation.e.privacymodules.trackers.Tracker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
@@ -52,13 +50,12 @@ class TrackersFeature(
val dayStatistics: TrackersPeriodicStatistics? = null,
val monthStatistics: TrackersPeriodicStatistics? = null,
val yearStatistics: TrackersPeriodicStatistics? = null,
- val apps: List<ApplicationDescription>? = null,
- val trackers: List<Tracker> = emptyList()
+ val apps: List<AppWithCounts>? = null,
)
sealed class SingleEvent {
data class ErrorEvent(val error: String) : SingleEvent()
- data class OpenAppDetailsEvent(val appDesc: ApplicationDescription) : SingleEvent()
+ data class OpenAppDetailsEvent(val appDesc: AppWithCounts) : SingleEvent()
object NewStatisticsAvailableSingleEvent : SingleEvent()
}
@@ -75,9 +72,9 @@ class TrackersFeature(
val yearStatistics: TrackersPeriodicStatistics? = null
) : Effect()
data class AvailableAppsListEffect(
- val apps: List<ApplicationDescription>
+ val apps: List<AppWithCounts>
) : Effect()
- data class OpenAppDetailsEffect(val appDesc: ApplicationDescription) : Effect()
+ data class OpenAppDetailsEffect(val appDesc: AppWithCounts) : Effect()
object QuickPrivacyDisabledWarningEffect : Effect()
data class ErrorEffect(val message: String) : Effect()
object NewStatisticsAvailablesEffect : Effect()
@@ -87,8 +84,7 @@ class TrackersFeature(
fun create(
initialState: State = State(),
coroutineScope: CoroutineScope,
- trackersStatisticsUseCase: TrackersStatisticsUseCase,
- appListUseCase: AppListUseCase
+ trackersStatisticsUseCase: TrackersStatisticsUseCase
) = TrackersFeature(
initialState, coroutineScope,
reducer = { state, effect ->
@@ -106,7 +102,19 @@ class TrackersFeature(
},
actor = { state, action ->
when (action) {
- Action.InitAction -> merge<TrackersFeature.Effect>(
+ Action.InitAction -> merge<Effect>(
+ flowOf(Effect.NewStatisticsAvailablesEffect),
+ trackersStatisticsUseCase.listenUpdates().map {
+ Effect.NewStatisticsAvailablesEffect
+ }
+ )
+
+ is Action.ClickAppAction -> flowOf(
+ state.apps?.find { it.packageName == action.packageName }?.let {
+ Effect.OpenAppDetailsEffect(it)
+ } ?: run { Effect.ErrorEffect("Can't find back app.") }
+ )
+ is Action.FetchStatistics -> merge<Effect>(
flow {
trackersStatisticsUseCase.getDayMonthYearStatistics()
.let { (day, month, year) ->
@@ -114,36 +122,15 @@ class TrackersFeature(
Effect.TrackersStatisticsLoadedEffect(
dayStatistics = day,
monthStatistics = month,
- yearStatistics = year
+ yearStatistics = year,
)
)
}
},
- appListUseCase.getAppsUsingInternet().map { apps ->
- Effect.AvailableAppsListEffect(apps)
- },
- trackersStatisticsUseCase.listenUpdates().map {
- Effect.NewStatisticsAvailablesEffect
+ trackersStatisticsUseCase.getAppsWithCounts().map {
+ Effect.AvailableAppsListEffect(it)
}
)
-
- is Action.ClickAppAction -> flowOf(
- state.apps?.find { it.packageName == action.packageName }?.let {
- Effect.OpenAppDetailsEffect(it)
- } ?: run { Effect.ErrorEffect("Can't find back app.") }
- )
- is Action.FetchStatistics -> flow {
- trackersStatisticsUseCase.getDayMonthYearStatistics()
- .let { (day, month, year) ->
- emit(
- Effect.TrackersStatisticsLoadedEffect(
- dayStatistics = day,
- monthStatistics = month,
- yearStatistics = year,
- )
- )
- }
- }
}
},
singleEventProducer = { _, _, effect ->
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt
index 3b22f89..0f686b4 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt
@@ -18,10 +18,11 @@
package foundation.e.privacycentralapp.features.trackers
import android.os.Bundle
+import android.util.Log
import android.view.View
import android.widget.Toast
-import androidx.fragment.app.add
import androidx.fragment.app.commit
+import androidx.fragment.app.replace
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
@@ -70,7 +71,7 @@ class TrackersFragment :
}
is TrackersFeature.SingleEvent.OpenAppDetailsEvent -> {
requireActivity().supportFragmentManager.commit {
- add<AppTrackersFragment>(R.id.container, args = AppTrackersFragment.buildArgs(event.appDesc.label.toString(), event.appDesc.packageName))
+ replace<AppTrackersFragment>(R.id.container, args = AppTrackersFragment.buildArgs(event.appDesc.label.toString(), event.appDesc.packageName))
setReorderingAllowed(true)
addToBackStack("apptrackers")
}
@@ -113,6 +114,7 @@ class TrackersFragment :
}
override fun onResume() {
+ Log.d("TestCounts", "OnResume")
super.onResume()
viewModel.submitAction(TrackersFeature.Action.FetchStatistics)
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
index e3a97cc..c2a1822 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt
@@ -20,15 +20,13 @@ package foundation.e.privacycentralapp.features.trackers
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.TrackersStatisticsUseCase
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
class TrackersViewModel(
- private val trackersStatisticsUseCase: TrackersStatisticsUseCase,
- private val appListUseCase: AppListUseCase
+ private val trackersStatisticsUseCase: TrackersStatisticsUseCase
) : ViewModel() {
private val _actions = MutableSharedFlow<TrackersFeature.Action>()
@@ -38,8 +36,7 @@ class TrackersViewModel(
TrackersFeature.create(
coroutineScope = viewModelScope,
- trackersStatisticsUseCase = trackersStatisticsUseCase,
- appListUseCase = appListUseCase
+ trackersStatisticsUseCase = trackersStatisticsUseCase
)
}
@@ -51,11 +48,10 @@ class TrackersViewModel(
}
class TrackersViewModelFactory(
- private val trackersStatisticsUseCase: TrackersStatisticsUseCase,
- private val appListUseCase: AppListUseCase
+ private val trackersStatisticsUseCase: TrackersStatisticsUseCase
) :
Factory<TrackersViewModel> {
override fun create(): TrackersViewModel {
- return TrackersViewModel(trackersStatisticsUseCase, appListUseCase)
+ return TrackersViewModel(trackersStatisticsUseCase)
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt
index 16cd4a0..790a5a0 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt
@@ -17,6 +17,7 @@
package foundation.e.privacycentralapp.features.trackers.apptrackers
+import android.net.Uri
import android.util.Log
import foundation.e.flowmvi.Actor
import foundation.e.flowmvi.Reducer
@@ -63,17 +64,24 @@ class AppTrackersFeature(
return null
}
}
+
+ fun getTrackersCount() = trackers?.size ?: 0
+ fun getBlockedTrackersCount(): Int = if (isBlockingActivated)
+ getTrackersCount() - (whitelist?.size ?: 0)
+ else 0
}
sealed class SingleEvent {
data class ErrorEvent(val error: Any) : SingleEvent()
object NewStatisticsAvailableSingleEvent : SingleEvent()
+ data class OpenUrlEvent(val url: Uri) : SingleEvent()
}
sealed class Action {
data class InitAction(val packageName: String) : Action()
data class BlockAllToggleAction(val isBlocked: Boolean) : Action()
data class ToggleTrackerAction(val tracker: Tracker, val isBlocked: Boolean) : Action()
+ data class ClickTracker(val tracker: Tracker) : Action()
object FetchStatistics : Action()
}
@@ -87,9 +95,12 @@ class AppTrackersFeature(
object NewStatisticsAvailablesEffect : Effect()
data class QuickPrivacyUpdatedEffect(val enabled: Boolean) : Effect()
object QuickPrivacyDisabledWarningEffect : Effect()
+ data class OpenUrlEffect(val url: Uri) : Effect()
}
companion object {
+
+ private const val exodusBaseUrl = "https://reports.exodus-privacy.eu.org/fr/trackers/"
fun create(
initialState: State = State(),
coroutineScope: CoroutineScope,
@@ -179,6 +190,17 @@ class AppTrackersFeature(
} ?: run { flowOf(Effect.ErrorEffect("No appDesc.")) }
} else flowOf(Effect.NoEffect)
}
+ is Action.ClickTracker -> {
+ flowOf(
+ action.tracker.exodusId?.let {
+ try {
+ Effect.OpenUrlEffect(Uri.parse(exodusBaseUrl + it))
+ } catch (e: Exception) {
+ Effect.ErrorEffect("Invalid Url")
+ }
+ } ?: Effect.NoEffect
+ )
+ }
is Action.FetchStatistics -> flowOf(
state.appDesc?.uid?.let {
Effect.AvailableTrackersListEffect(
@@ -196,6 +218,8 @@ class AppTrackersFeature(
SingleEvent.ErrorEvent(R.string.apptrackers_error_quickprivacy_disabled)
is Effect.NewStatisticsAvailablesEffect ->
SingleEvent.NewStatisticsAvailableSingleEvent
+ is Effect.OpenUrlEffect ->
+ SingleEvent.OpenUrlEvent(effect.url)
else -> null
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt
index 440edf7..8e2dc3b 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt
@@ -17,6 +17,8 @@
package foundation.e.privacycentralapp.features.trackers.apptrackers
+import android.content.ActivityNotFoundException
+import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
@@ -76,6 +78,12 @@ class AppTrackersFragment :
is SingleEvent.NewStatisticsAvailableSingleEvent -> {
viewModel.submitAction(Action.FetchStatistics)
}
+ is SingleEvent.OpenUrlEvent ->
+ try {
+ startActivity(Intent(Intent.ACTION_VIEW, event.url))
+ } catch (e: ActivityNotFoundException) {
+ displayToast("No application to see webpages")
+ }
}
}
}
@@ -104,14 +112,13 @@ class AppTrackersFragment :
binding.trackers.apply {
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
- adapter = ToggleTrackersAdapter(R.layout.apptrackers_item_tracker_toggle) { tracker, isBlocked ->
- viewModel.submitAction(
- Action.ToggleTrackerAction(
- tracker,
- isBlocked
- )
- )
- }
+ adapter = ToggleTrackersAdapter(
+ R.layout.apptrackers_item_tracker_toggle,
+ onToggleSwitch = { tracker, isBlocked ->
+ viewModel.submitAction(Action.ToggleTrackerAction(tracker, isBlocked))
+ },
+ onClickTitle = { viewModel.submitAction(Action.ClickTracker(it)) }
+ )
}
}
@@ -121,6 +128,14 @@ class AppTrackersFragment :
}
override fun render(state: State) {
+
+ binding.trackersCountSummary.text = if (state.getTrackersCount() == 0) ""
+ else getString(
+ R.string.apptrackers_trackers_count_summary,
+ state.getBlockedTrackersCount(),
+ state.getTrackersCount()
+ )
+
binding.blockAllToggle.isChecked = state.isBlockingActivated
binding.trackersListTitle.isVisible = state.isBlockingActivated
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt
index 580a60c..e77b61f 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt
@@ -17,11 +17,14 @@
package foundation.e.privacycentralapp.features.trackers.apptrackers
+import android.text.SpannableString
+import android.text.style.UnderlineSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Switch
import android.widget.TextView
+import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import foundation.e.privacycentralapp.R
@@ -29,9 +32,9 @@ import foundation.e.privacymodules.trackers.Tracker
class ToggleTrackersAdapter(
private val itemsLayout: Int,
- private val listener: (Tracker, Boolean) -> Unit
-) :
- RecyclerView.Adapter<ToggleTrackersAdapter.ViewHolder>() {
+ private val onToggleSwitch: (Tracker, Boolean) -> Unit,
+ private val onClickTitle: (Tracker) -> Unit
+) : RecyclerView.Adapter<ToggleTrackersAdapter.ViewHolder>() {
var isEnabled = true
@@ -42,7 +45,17 @@ class ToggleTrackersAdapter(
val toggleOverlay: View = view.findViewById(R.id.toggle_clicker)
fun bind(item: Pair<Tracker, Boolean>, isEnabled: Boolean) {
- title.text = item.first.label
+ val text = item.first.label
+ if (item.first.exodusId != null) {
+ title.setTextColor(ContextCompat.getColor(title.context, R.color.accent))
+ val spannable = SpannableString(text)
+ spannable.setSpan(UnderlineSpan(), 0, spannable.length, 0)
+ title.text = spannable
+ } else {
+ title.setTextColor(ContextCompat.getColor(title.context, R.color.black))
+ title.text = text
+ }
+
toggle.isChecked = item.second
toggle.isEnabled = isEnabled
toggleOverlay.isVisible = !isEnabled
@@ -62,10 +75,14 @@ class ToggleTrackersAdapter(
.inflate(itemsLayout, parent, false)
val holder = ViewHolder(view)
holder.toggle.setOnClickListener {
- listener(dataSet[holder.adapterPosition].first, holder.toggle.isChecked)
+ onToggleSwitch(dataSet[holder.adapterPosition].first, holder.toggle.isChecked)
}
holder.toggleOverlay.setOnClickListener {
- listener(dataSet[holder.adapterPosition].first, false)
+ onToggleSwitch(dataSet[holder.adapterPosition].first, false)
+ }
+
+ holder.title.setOnClickListener {
+ onClickTitle(dataSet[holder.adapterPosition].first)
}
return holder