diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersAndAppsLists.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersAndAppsLists.kt new file mode 100644 index 0000000000000000000000000000000000000000..2113b3a3f0e30837a5b1aeef330c2175cf10a2b4 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersAndAppsLists.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 MURENA SAS + * + * 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 . + */ +package foundation.e.advancedprivacy.domain.entities + +import foundation.e.advancedprivacy.features.trackers.AppWithTrackersCount +import foundation.e.advancedprivacy.features.trackers.TrackerWithAppsCount + +data class TrackersAndAppsLists( + val trackers: List, + val allApps: List, + val appsWithTrackers: List +) diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt index 8292a6d1d9cd85913699926faf3728cbaf2f3742..ea07e8fa10a7f5d909a5601d490bcbe0fb9bbcf6 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt @@ -18,6 +18,7 @@ package foundation.e.advancedprivacy.domain.usecases import foundation.e.advancedprivacy.data.repositories.AppListsRepository import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.TrackersAndAppsLists import foundation.e.advancedprivacy.features.trackers.AppWithTrackersCount import foundation.e.advancedprivacy.features.trackers.TrackerWithAppsCount import foundation.e.advancedprivacy.trackers.data.StatsDatabase @@ -31,14 +32,16 @@ class TrackersAndAppsListsUseCase( private val appListsRepository: AppListsRepository, ) { - suspend fun getAppsAndTrackersCounts(): Pair, List> { + suspend fun getTrackersAndAppsLists(): TrackersAndAppsLists { val trackersAndAppsIds = statsDatabase.getDistinctTrackerAndApp() val trackersAndApps = mapIdsToEntities(trackersAndAppsIds) val (countByApp, countByTracker) = foldToCountByEntityMaps(trackersAndApps) - val appList = buildAppList(countByApp) - val trackerList = buildTrackerList(countByTracker) - return appList to trackerList + return TrackersAndAppsLists( + trackers = buildTrackerList(countByTracker), + allApps = buildAllAppList(countByApp), + appsWithTrackers = buildAppList(countByApp) + ) } private fun buildTrackerList(countByTracker: Map): List { @@ -47,12 +50,18 @@ class TrackersAndAppsListsUseCase( }.sortedByDescending { it.appsCount } } - private suspend fun buildAppList(countByApp: Map): List { + private suspend fun buildAllAppList(countByApp: Map): List { return appListsRepository.apps().first().map { app: ApplicationDescription -> AppWithTrackersCount(app = app, trackersCount = countByApp[app] ?: 0) }.sortedByDescending { it.trackersCount } } + private fun buildAppList(countByApp: Map): List { + return countByApp.map { (app, count) -> + AppWithTrackersCount(app = app, trackersCount = count) + }.sortedByDescending { it.trackersCount } + } + private suspend fun mapIdsToEntities(trackersAndAppsIds: List>): List> { return trackersAndAppsIds.mapNotNull { (trackerId, apId) -> trackersRepository.getTracker(trackerId)?.let { tracker -> diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/ListsTabPagerAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/ListsTabPagerAdapter.kt index 242041003892acfa6376d2513a78c26e8823164e..e9a046f73ea0c2d77216987c3ff96a9303284ed9 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/ListsTabPagerAdapter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/ListsTabPagerAdapter.kt @@ -26,6 +26,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.divider.MaterialDividerItemDecoration import foundation.e.advancedprivacy.R +import foundation.e.advancedprivacy.databinding.TrackersAppsListBinding import foundation.e.advancedprivacy.databinding.TrackersListBinding const val TAB_APPS = 0 @@ -35,25 +36,29 @@ class ListsTabPagerAdapter( private val context: Context, private val viewModel: TrackersViewModel, ) : RecyclerView.Adapter() { - private var apps: List = emptyList() - private var trackers: List = emptyList() + private var uiState: TrackersState = TrackersState() - fun updateDataSet(apps: List?, trackers: List?) { - this.apps = apps ?: emptyList() - this.trackers = trackers ?: emptyList() + fun updateDataSet(state: TrackersState) { + uiState = state notifyDataSetChanged() } override fun getItemViewType(position: Int): Int = position override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListsTabViewHolder { - val binding = TrackersListBinding.inflate(LayoutInflater.from(context), parent, false) + val inflater = LayoutInflater.from(parent.context) return when (viewType) { TAB_APPS -> { - ListsTabViewHolder.AppsListViewHolder(binding, viewModel) + ListsTabViewHolder.AppsListViewHolder( + TrackersAppsListBinding.inflate(inflater, parent, false), + viewModel + ) } else -> { - ListsTabViewHolder.TrackersListViewHolder(binding, viewModel) + ListsTabViewHolder.TrackersListViewHolder( + TrackersListBinding.inflate(inflater, parent, false), + viewModel + ) } } } @@ -65,10 +70,10 @@ class ListsTabPagerAdapter( override fun onBindViewHolder(holder: ListsTabViewHolder, position: Int) { when (position) { TAB_APPS -> { - (holder as ListsTabViewHolder.AppsListViewHolder).onBind(apps) + (holder as ListsTabViewHolder.AppsListViewHolder).onBind(uiState) } TAB_TRACKERS -> { - (holder as ListsTabViewHolder.TrackersListViewHolder).onBind(trackers) + (holder as ListsTabViewHolder.TrackersListViewHolder).onBind(uiState.trackers ?: emptyList()) } } } @@ -93,16 +98,25 @@ class ListsTabPagerAdapter( } class AppsListViewHolder( - private val binding: TrackersListBinding, + private val binding: TrackersAppsListBinding, private val viewModel: TrackersViewModel ) : ListsTabViewHolder(binding.root) { init { setupRecyclerView(binding.list) binding.list.adapter = AppsAdapter(viewModel) + binding.toggleNoTrackerApps.setOnClickListener { viewModel.onToggleHideNoTrackersApps() } } - fun onBind(apps: List) { - (binding.list.adapter as AppsAdapter).dataSet = apps + fun onBind(uiState: TrackersState) { + (binding.list.adapter as AppsAdapter).dataSet = ( + if (uiState.hideNoTrackersApps) + uiState.appsWithTrackers else uiState.allApps + ) ?: emptyList() + + binding.toggleNoTrackerApps.apply { + isCloseIconVisible = uiState.hideNoTrackersApps + isChecked = uiState.hideNoTrackersApps + } } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt index b016c5e21560b085a94d61d685ba6cc10485dc09..b88c55ea21b100a099deda3d1aaee19964f78eb8 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt @@ -209,7 +209,7 @@ class TrackersFragment : NavToolbarFragment(R.layout.fragment_trackers) { state.yearStatistics?.let { renderGraph(it, yearGraphHolder!!, binding.graphYear) } updatePagerHeight() - tabAdapter.updateDataSet(state.apps, state.trackers) + tabAdapter.updateDataSet(state) } private fun renderGraph( diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersState.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersState.kt index 7f5fdfeb2253a91307a217b05d09e9723908cddf..1685c081fe65f8ab45af0954d54599f7001e96d0 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersState.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersState.kt @@ -26,8 +26,10 @@ data class TrackersState( val dayStatistics: TrackersPeriodicStatistics? = null, val monthStatistics: TrackersPeriodicStatistics? = null, val yearStatistics: TrackersPeriodicStatistics? = null, - val apps: List? = null, - val trackers: List? = null + val allApps: List? = null, + val trackers: List? = null, + val appsWithTrackers: List? = null, + val hideNoTrackersApps: Boolean = false ) data class AppWithTrackersCount( diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersViewModel.kt index 31da8ca79a76928073c71c0c4a982f4d46a37d21..77da291fb0148bf7a5abb8dcfaae9a8b9555dfd2 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersViewModel.kt @@ -62,9 +62,13 @@ class TrackersViewModel( } } - trackersAndAppsListsUseCase.getAppsAndTrackersCounts().let { (appList, trackerList) -> + trackersAndAppsListsUseCase.getTrackersAndAppsLists().let { lists -> _state.update { - it.copy(apps = appList, trackers = trackerList) + it.copy( + trackers = lists.trackers, + allApps = lists.allApps, + appsWithTrackers = lists.appsWithTrackers + ) } } } @@ -82,6 +86,10 @@ class TrackersViewModel( _singleEvents.emit(SingleEvent.OpenUrl(Uri.parse(URL_LEARN_MORE_ABOUT_TRACKERS))) } + fun onToggleHideNoTrackersApps() = viewModelScope.launch { + _state.update { it.copy(hideNoTrackersApps = !it.hideNoTrackersApps) } + } + sealed class SingleEvent { data class ErrorEvent(val error: String) : SingleEvent() data class OpenUrl(val url: Uri) : SingleEvent() diff --git a/app/src/main/res/color/chip_background.xml b/app/src/main/res/color/chip_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..c7f8c8aa0fa1837e1a0afee4dbb2c02da67ec9a8 --- /dev/null +++ b/app/src/main/res/color/chip_background.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/chip_text.xml b/app/src/main/res/color/chip_text.xml new file mode 100644 index 0000000000000000000000000000000000000000..1de4d221171fc7a8bad8c3280ee78104e4570698 --- /dev/null +++ b/app/src/main/res/color/chip_text.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000000000000000000000000000000000000..e9ad3c66b33bf75fd643e0e62c2efdc1d824f93c --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/trackers_apps_list.xml b/app/src/main/res/layout/trackers_apps_list.xml new file mode 100644 index 0000000000000000000000000000000000000000..63f4b241f715b48dc4327ab12f5e1864f3af560d --- /dev/null +++ b/app/src/main/res/layout/trackers_apps_list.xml @@ -0,0 +1,47 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aa33837317195d890793229d9edd2dea5f989d9a..0ea109467514345fc45a36fb0f6e727984da8a8e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -97,12 +97,12 @@ 24 hours past month past year - @string/ipscrambling_app_list_infos - Trackers Activity Summary Apps Trackers + Trackers Activity Summary + Apps with trackers %s trackers detected detected in %s apps