Loading app/src/main/java/foundation/e/apps/PrivacyInfoViewModel.kt +5 −53 Original line number Diff line number Diff line Loading @@ -8,10 +8,7 @@ import foundation.e.apps.api.Result import foundation.e.apps.api.exodus.models.AppPrivacyInfo import foundation.e.apps.api.exodus.repositories.IAppPrivacyInfoRepository import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL import javax.inject.Inject import kotlin.math.ceil import kotlin.math.round @HiltViewModel class PrivacyInfoViewModel @Inject constructor( Loading @@ -27,40 +24,17 @@ class PrivacyInfoViewModel @Inject constructor( private suspend fun fetchEmitAppPrivacyInfo( fusedApp: FusedApp ): Result<AppPrivacyInfo> { if (fusedApp.trackers.isNotEmpty() && fusedApp.permsFromExodus.isNotEmpty()) { val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.permsFromExodus) return Result.success(appInfo) } val appPrivacyPrivacyInfoResult = privacyInfoRepository.getAppPrivacyInfo(fusedApp.package_name) return handleAppPrivacyInfoResult(appPrivacyPrivacyInfoResult, fusedApp) privacyInfoRepository.getAppPrivacyInfo(fusedApp, fusedApp.package_name) return handleAppPrivacyInfoResult(appPrivacyPrivacyInfoResult) } private fun handleAppPrivacyInfoResult( appPrivacyPrivacyInfoResult: Result<AppPrivacyInfo>, fusedApp: FusedApp ): Result<AppPrivacyInfo> { return if (appPrivacyPrivacyInfoResult.isSuccess()) { handleAppPrivacyInfoSuccess(appPrivacyPrivacyInfoResult, fusedApp) } else { return if (!appPrivacyPrivacyInfoResult.isSuccess()) { Result.error("Tracker not found!") } } private fun handleAppPrivacyInfoSuccess( appPrivacyPrivacyInfoResult: Result<AppPrivacyInfo>, fusedApp: FusedApp ): Result<AppPrivacyInfo> { fusedApp.trackers = appPrivacyPrivacyInfoResult.data?.trackerList ?: LIST_OF_NULL fusedApp.permsFromExodus = appPrivacyPrivacyInfoResult.data?.permissionList ?: LIST_OF_NULL if (fusedApp.perms.isEmpty() && fusedApp.permsFromExodus != LIST_OF_NULL) { /* * fusedApp.perms is generally populated from remote source like Play Store. * If it is empty then set the value from permissions from exodus api. */ fusedApp.perms = fusedApp.permsFromExodus } return appPrivacyPrivacyInfoResult } else appPrivacyPrivacyInfoResult } fun getTrackerListText(fusedApp: FusedApp?): String { Loading @@ -74,30 +48,8 @@ class PrivacyInfoViewModel @Inject constructor( fun getPrivacyScore(fusedApp: FusedApp?): Int { fusedApp?.let { return calculatePrivacyScore(it) return privacyInfoRepository.calculatePrivacyScore(it) } return -1 } fun calculatePrivacyScore(fusedApp: FusedApp): Int { if (fusedApp.permsFromExodus == LIST_OF_NULL) { return -1 } val calculateTrackersScore = calculateTrackersScore(fusedApp.trackers.size) val calculatePermissionsScore = calculatePermissionsScore( countAndroidPermissions(fusedApp) ) return calculateTrackersScore + calculatePermissionsScore } private fun countAndroidPermissions(fusedApp: FusedApp) = fusedApp.permsFromExodus.filter { it.contains("android.permission") }.size private fun calculateTrackersScore(numberOfTrackers: Int): Int { return if (numberOfTrackers > 5) 0 else 9 - numberOfTrackers } private fun calculatePermissionsScore(numberOfPermission: Int): Int { return if (numberOfPermission > 9) 0 else round(0.2 * ceil((10 - numberOfPermission) / 2.0)).toInt() } } app/src/main/java/foundation/e/apps/api/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt +49 −2 Original line number Diff line number Diff line Loading @@ -6,10 +6,13 @@ import foundation.e.apps.api.exodus.Report import foundation.e.apps.api.exodus.Tracker import foundation.e.apps.api.exodus.TrackerDao import foundation.e.apps.api.exodus.models.AppPrivacyInfo import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.api.getResult import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL import javax.inject.Inject import javax.inject.Singleton import kotlin.math.ceil import kotlin.math.round @Singleton class AppPrivacyInfoRepositoryImpl @Inject constructor( Loading @@ -18,14 +21,36 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( ) : IAppPrivacyInfoRepository { private var trackers: List<Tracker> = listOf() override suspend fun getAppPrivacyInfo(appHandle: String): Result<AppPrivacyInfo> { override suspend fun getAppPrivacyInfo(fusedApp: FusedApp, appHandle: String): Result<AppPrivacyInfo> { if (fusedApp.trackers.isNotEmpty() && fusedApp.permsFromExodus.isNotEmpty()) { val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.permsFromExodus) return Result.success(appInfo) } val appTrackerInfoResult = getResult { exodusTrackerApi.getTrackerInfoOfApp(appHandle) } if (appTrackerInfoResult.isSuccess()) { return handleAppPrivacyInfoResultSuccess(appTrackerInfoResult) val appPrivacyPrivacyInfoResult = handleAppPrivacyInfoResultSuccess(appTrackerInfoResult) updateFusedApp(fusedApp, appPrivacyPrivacyInfoResult) return appPrivacyPrivacyInfoResult } return Result.error(extractErrorMessage(appTrackerInfoResult)) } private fun updateFusedApp( fusedApp: FusedApp, appPrivacyPrivacyInfoResult: Result<AppPrivacyInfo> ) { fusedApp.trackers = appPrivacyPrivacyInfoResult.data?.trackerList ?: LIST_OF_NULL fusedApp.permsFromExodus = appPrivacyPrivacyInfoResult.data?.permissionList ?: LIST_OF_NULL if (fusedApp.perms.isEmpty() && fusedApp.permsFromExodus != LIST_OF_NULL) { /* * fusedApp.perms is generally populated from remote source like Play Store. * If it is empty then set the value from permissions from exodus api. */ fusedApp.perms = fusedApp.permsFromExodus } } private suspend fun handleAppPrivacyInfoResultSuccess( appTrackerResult: Result<List<Report>>, ): Result<AppPrivacyInfo> { Loading Loading @@ -95,4 +120,26 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( sortedTrackerData[0].trackers.contains(it.id) }.map { it.name } } override fun calculatePrivacyScore(fusedApp: FusedApp): Int { if (fusedApp.permsFromExodus == LIST_OF_NULL) { return -1 } val calculateTrackersScore = calculateTrackersScore(fusedApp.trackers.size) val calculatePermissionsScore = calculatePermissionsScore( countAndroidPermissions(fusedApp) ) return calculateTrackersScore + calculatePermissionsScore } private fun countAndroidPermissions(fusedApp: FusedApp) = fusedApp.permsFromExodus.filter { it.contains("android.permission") }.size private fun calculateTrackersScore(numberOfTrackers: Int): Int { return if (numberOfTrackers > 5) 0 else 9 - numberOfTrackers } private fun calculatePermissionsScore(numberOfPermission: Int): Int { return if (numberOfPermission > 9) 0 else round(0.2 * ceil((10 - numberOfPermission) / 2.0)).toInt() } } app/src/main/java/foundation/e/apps/api/exodus/repositories/IAppPrivacyInfoRepository.kt +3 −1 Original line number Diff line number Diff line Loading @@ -2,7 +2,9 @@ package foundation.e.apps.api.exodus.repositories import foundation.e.apps.api.Result import foundation.e.apps.api.exodus.models.AppPrivacyInfo import foundation.e.apps.api.fused.data.FusedApp interface IAppPrivacyInfoRepository { suspend fun getAppPrivacyInfo(appHandle: String): Result<AppPrivacyInfo> suspend fun getAppPrivacyInfo(fusedApp: FusedApp, appHandle: String): Result<AppPrivacyInfo> fun calculatePrivacyScore(fusedApp: FusedApp): Int } app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt +1 −4 Original line number Diff line number Diff line Loading @@ -35,7 +35,6 @@ import foundation.e.apps.utils.enums.FilterLevel import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Status import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton Loading Loading @@ -343,7 +342,6 @@ class FusedAPIRepository @Inject constructor( authData: AuthData, browseUrl: String, ): ResultSupreme<List<FusedApp>> { Timber.d("hasNextStreamCluster: $hasNextStreamCluster hasNextStreamBundle: $hasNextStreamBundle clusterPointer: $clusterPointer: streambundleSize: ${streamBundle.streamClusters.size} streamClusterSize: ${streamCluster.clusterAppList.size}") if (hasNextStreamCluster) { getNextStreamCluster(authData).run { if (!isSuccess()) { Loading @@ -369,7 +367,6 @@ class FusedAPIRepository @Inject constructor( } } } Timber.d("===> calling last segment") return filterRestrictedGPlayApps(authData, streamCluster.clusterAppList) } Loading app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt +0 −33 Original line number Diff line number Diff line Loading @@ -22,16 +22,12 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.api.ResultSupreme import foundation.e.apps.api.fused.FusedAPIRepository import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.utils.enums.Origin import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @HiltViewModel Loading @@ -44,7 +40,6 @@ class ApplicationListViewModel @Inject constructor( var isLoading = false fun getList(category: String, browseUrl: String, authData: AuthData, source: String) { Timber.d("===> getlist: $isLoading") if (isLoading) { return } Loading @@ -53,7 +48,6 @@ class ApplicationListViewModel @Inject constructor( fusedAPIRepository.getAppList(category, browseUrl, authData, source).apply { isLoading = false appListLiveData.postValue(this) Timber.d("final result: ${this.data?.size}") } } } Loading @@ -68,33 +62,6 @@ class ApplicationListViewModel @Inject constructor( return fusedAPIRepository.isAnyFusedAppUpdated(newFusedApps, oldFusedApps) } /** * Add a placeholder app at the end if more data can be loaded. * "Placeholder" app shows a simple progress bar in the RecyclerView, indicating that * more apps are being loaded. * * Note that it mutates the [ResultSupreme] object passed to it. * * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5131 [2] * * @param result object from [getNextDataSet]. Data of this object will be updated * if [canLoadMore] is true. * * @return true if a placeholder app was added, false otherwise. */ private fun addPlaceHolderAppIfNeeded(result: ResultSupreme<List<FusedApp>>): Boolean { result.apply { if (isSuccess() && fusedAPIRepository.canLoadMore()) { // Add an empty app at the end if more data can be loaded on scroll val newData = data!!.toMutableList() newData.add(FusedApp(isPlaceHolder = true)) setData(newData) return true } } return false } fun loadMore(authData: AuthData, browseUrl: String) { viewModelScope.launch { if (isLoading) { Loading Loading
app/src/main/java/foundation/e/apps/PrivacyInfoViewModel.kt +5 −53 Original line number Diff line number Diff line Loading @@ -8,10 +8,7 @@ import foundation.e.apps.api.Result import foundation.e.apps.api.exodus.models.AppPrivacyInfo import foundation.e.apps.api.exodus.repositories.IAppPrivacyInfoRepository import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL import javax.inject.Inject import kotlin.math.ceil import kotlin.math.round @HiltViewModel class PrivacyInfoViewModel @Inject constructor( Loading @@ -27,40 +24,17 @@ class PrivacyInfoViewModel @Inject constructor( private suspend fun fetchEmitAppPrivacyInfo( fusedApp: FusedApp ): Result<AppPrivacyInfo> { if (fusedApp.trackers.isNotEmpty() && fusedApp.permsFromExodus.isNotEmpty()) { val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.permsFromExodus) return Result.success(appInfo) } val appPrivacyPrivacyInfoResult = privacyInfoRepository.getAppPrivacyInfo(fusedApp.package_name) return handleAppPrivacyInfoResult(appPrivacyPrivacyInfoResult, fusedApp) privacyInfoRepository.getAppPrivacyInfo(fusedApp, fusedApp.package_name) return handleAppPrivacyInfoResult(appPrivacyPrivacyInfoResult) } private fun handleAppPrivacyInfoResult( appPrivacyPrivacyInfoResult: Result<AppPrivacyInfo>, fusedApp: FusedApp ): Result<AppPrivacyInfo> { return if (appPrivacyPrivacyInfoResult.isSuccess()) { handleAppPrivacyInfoSuccess(appPrivacyPrivacyInfoResult, fusedApp) } else { return if (!appPrivacyPrivacyInfoResult.isSuccess()) { Result.error("Tracker not found!") } } private fun handleAppPrivacyInfoSuccess( appPrivacyPrivacyInfoResult: Result<AppPrivacyInfo>, fusedApp: FusedApp ): Result<AppPrivacyInfo> { fusedApp.trackers = appPrivacyPrivacyInfoResult.data?.trackerList ?: LIST_OF_NULL fusedApp.permsFromExodus = appPrivacyPrivacyInfoResult.data?.permissionList ?: LIST_OF_NULL if (fusedApp.perms.isEmpty() && fusedApp.permsFromExodus != LIST_OF_NULL) { /* * fusedApp.perms is generally populated from remote source like Play Store. * If it is empty then set the value from permissions from exodus api. */ fusedApp.perms = fusedApp.permsFromExodus } return appPrivacyPrivacyInfoResult } else appPrivacyPrivacyInfoResult } fun getTrackerListText(fusedApp: FusedApp?): String { Loading @@ -74,30 +48,8 @@ class PrivacyInfoViewModel @Inject constructor( fun getPrivacyScore(fusedApp: FusedApp?): Int { fusedApp?.let { return calculatePrivacyScore(it) return privacyInfoRepository.calculatePrivacyScore(it) } return -1 } fun calculatePrivacyScore(fusedApp: FusedApp): Int { if (fusedApp.permsFromExodus == LIST_OF_NULL) { return -1 } val calculateTrackersScore = calculateTrackersScore(fusedApp.trackers.size) val calculatePermissionsScore = calculatePermissionsScore( countAndroidPermissions(fusedApp) ) return calculateTrackersScore + calculatePermissionsScore } private fun countAndroidPermissions(fusedApp: FusedApp) = fusedApp.permsFromExodus.filter { it.contains("android.permission") }.size private fun calculateTrackersScore(numberOfTrackers: Int): Int { return if (numberOfTrackers > 5) 0 else 9 - numberOfTrackers } private fun calculatePermissionsScore(numberOfPermission: Int): Int { return if (numberOfPermission > 9) 0 else round(0.2 * ceil((10 - numberOfPermission) / 2.0)).toInt() } }
app/src/main/java/foundation/e/apps/api/exodus/repositories/AppPrivacyInfoRepositoryImpl.kt +49 −2 Original line number Diff line number Diff line Loading @@ -6,10 +6,13 @@ import foundation.e.apps.api.exodus.Report import foundation.e.apps.api.exodus.Tracker import foundation.e.apps.api.exodus.TrackerDao import foundation.e.apps.api.exodus.models.AppPrivacyInfo import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.api.getResult import foundation.e.apps.utils.modules.CommonUtilsModule.LIST_OF_NULL import javax.inject.Inject import javax.inject.Singleton import kotlin.math.ceil import kotlin.math.round @Singleton class AppPrivacyInfoRepositoryImpl @Inject constructor( Loading @@ -18,14 +21,36 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( ) : IAppPrivacyInfoRepository { private var trackers: List<Tracker> = listOf() override suspend fun getAppPrivacyInfo(appHandle: String): Result<AppPrivacyInfo> { override suspend fun getAppPrivacyInfo(fusedApp: FusedApp, appHandle: String): Result<AppPrivacyInfo> { if (fusedApp.trackers.isNotEmpty() && fusedApp.permsFromExodus.isNotEmpty()) { val appInfo = AppPrivacyInfo(fusedApp.trackers, fusedApp.permsFromExodus) return Result.success(appInfo) } val appTrackerInfoResult = getResult { exodusTrackerApi.getTrackerInfoOfApp(appHandle) } if (appTrackerInfoResult.isSuccess()) { return handleAppPrivacyInfoResultSuccess(appTrackerInfoResult) val appPrivacyPrivacyInfoResult = handleAppPrivacyInfoResultSuccess(appTrackerInfoResult) updateFusedApp(fusedApp, appPrivacyPrivacyInfoResult) return appPrivacyPrivacyInfoResult } return Result.error(extractErrorMessage(appTrackerInfoResult)) } private fun updateFusedApp( fusedApp: FusedApp, appPrivacyPrivacyInfoResult: Result<AppPrivacyInfo> ) { fusedApp.trackers = appPrivacyPrivacyInfoResult.data?.trackerList ?: LIST_OF_NULL fusedApp.permsFromExodus = appPrivacyPrivacyInfoResult.data?.permissionList ?: LIST_OF_NULL if (fusedApp.perms.isEmpty() && fusedApp.permsFromExodus != LIST_OF_NULL) { /* * fusedApp.perms is generally populated from remote source like Play Store. * If it is empty then set the value from permissions from exodus api. */ fusedApp.perms = fusedApp.permsFromExodus } } private suspend fun handleAppPrivacyInfoResultSuccess( appTrackerResult: Result<List<Report>>, ): Result<AppPrivacyInfo> { Loading Loading @@ -95,4 +120,26 @@ class AppPrivacyInfoRepositoryImpl @Inject constructor( sortedTrackerData[0].trackers.contains(it.id) }.map { it.name } } override fun calculatePrivacyScore(fusedApp: FusedApp): Int { if (fusedApp.permsFromExodus == LIST_OF_NULL) { return -1 } val calculateTrackersScore = calculateTrackersScore(fusedApp.trackers.size) val calculatePermissionsScore = calculatePermissionsScore( countAndroidPermissions(fusedApp) ) return calculateTrackersScore + calculatePermissionsScore } private fun countAndroidPermissions(fusedApp: FusedApp) = fusedApp.permsFromExodus.filter { it.contains("android.permission") }.size private fun calculateTrackersScore(numberOfTrackers: Int): Int { return if (numberOfTrackers > 5) 0 else 9 - numberOfTrackers } private fun calculatePermissionsScore(numberOfPermission: Int): Int { return if (numberOfPermission > 9) 0 else round(0.2 * ceil((10 - numberOfPermission) / 2.0)).toInt() } }
app/src/main/java/foundation/e/apps/api/exodus/repositories/IAppPrivacyInfoRepository.kt +3 −1 Original line number Diff line number Diff line Loading @@ -2,7 +2,9 @@ package foundation.e.apps.api.exodus.repositories import foundation.e.apps.api.Result import foundation.e.apps.api.exodus.models.AppPrivacyInfo import foundation.e.apps.api.fused.data.FusedApp interface IAppPrivacyInfoRepository { suspend fun getAppPrivacyInfo(appHandle: String): Result<AppPrivacyInfo> suspend fun getAppPrivacyInfo(fusedApp: FusedApp, appHandle: String): Result<AppPrivacyInfo> fun calculatePrivacyScore(fusedApp: FusedApp): Int }
app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt +1 −4 Original line number Diff line number Diff line Loading @@ -35,7 +35,6 @@ import foundation.e.apps.utils.enums.FilterLevel import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Status import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton Loading Loading @@ -343,7 +342,6 @@ class FusedAPIRepository @Inject constructor( authData: AuthData, browseUrl: String, ): ResultSupreme<List<FusedApp>> { Timber.d("hasNextStreamCluster: $hasNextStreamCluster hasNextStreamBundle: $hasNextStreamBundle clusterPointer: $clusterPointer: streambundleSize: ${streamBundle.streamClusters.size} streamClusterSize: ${streamCluster.clusterAppList.size}") if (hasNextStreamCluster) { getNextStreamCluster(authData).run { if (!isSuccess()) { Loading @@ -369,7 +367,6 @@ class FusedAPIRepository @Inject constructor( } } } Timber.d("===> calling last segment") return filterRestrictedGPlayApps(authData, streamCluster.clusterAppList) } Loading
app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt +0 −33 Original line number Diff line number Diff line Loading @@ -22,16 +22,12 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.api.ResultSupreme import foundation.e.apps.api.fused.FusedAPIRepository import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.utils.enums.Origin import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @HiltViewModel Loading @@ -44,7 +40,6 @@ class ApplicationListViewModel @Inject constructor( var isLoading = false fun getList(category: String, browseUrl: String, authData: AuthData, source: String) { Timber.d("===> getlist: $isLoading") if (isLoading) { return } Loading @@ -53,7 +48,6 @@ class ApplicationListViewModel @Inject constructor( fusedAPIRepository.getAppList(category, browseUrl, authData, source).apply { isLoading = false appListLiveData.postValue(this) Timber.d("final result: ${this.data?.size}") } } } Loading @@ -68,33 +62,6 @@ class ApplicationListViewModel @Inject constructor( return fusedAPIRepository.isAnyFusedAppUpdated(newFusedApps, oldFusedApps) } /** * Add a placeholder app at the end if more data can be loaded. * "Placeholder" app shows a simple progress bar in the RecyclerView, indicating that * more apps are being loaded. * * Note that it mutates the [ResultSupreme] object passed to it. * * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5131 [2] * * @param result object from [getNextDataSet]. Data of this object will be updated * if [canLoadMore] is true. * * @return true if a placeholder app was added, false otherwise. */ private fun addPlaceHolderAppIfNeeded(result: ResultSupreme<List<FusedApp>>): Boolean { result.apply { if (isSuccess() && fusedAPIRepository.canLoadMore()) { // Add an empty app at the end if more data can be loaded on scroll val newData = data!!.toMutableList() newData.add(FusedApp(isPlaceHolder = true)) setData(newData) return true } } return false } fun loadMore(authData: AuthData, browseUrl: String) { viewModelScope.launch { if (isLoading) { Loading