diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index 7813ffbf56cdf7620ed388b92bb03a51518819cc..21ad79f516a33adfd9001d3d42594fe136bff908 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -169,33 +169,20 @@ TooGenericExceptionThrown:AnonymousLoginManager.kt$AnonymousLoginManager$throw Exception( "Error fetching Anonymous credentials\n" + "Network code: ${response.code}\n" + "Success: ${response.isSuccessful}" + response.errorString.run { if (isNotBlank()) "\nError message: $this" else "" } ) TooGenericExceptionThrown:PlayStoreLoginWrapper.kt$PlayStoreLoginWrapper$throw Exception("Validation network code: ${response.code}") TooGenericExceptionThrown:PlayStoreLoginWrapper.kt$PlayStoreLoginWrapper$throw Exception(error) - TooManyFunctions:AppInstallProcessor.kt$AppInstallProcessor - TooManyFunctions:AppPrivacyInfoRepositoryImpl.kt$AppPrivacyInfoRepositoryImpl : IAppPrivacyInfoRepository TooManyFunctions:ApplicationApi.kt$ApplicationApi TooManyFunctions:ApplicationApiImpl.kt$ApplicationApiImpl : ApplicationApi - TooManyFunctions:ApplicationFragment.kt$ApplicationFragment : TimeoutFragment TooManyFunctions:ApplicationListFragment.kt$ApplicationListFragment : TimeoutFragmentApplicationInstaller - TooManyFunctions:ApplicationListRVAdapter.kt$ApplicationListRVAdapter : ListAdapter TooManyFunctions:ApplicationRepository.kt$ApplicationRepository - TooManyFunctions:DownloadManager.kt$DownloadManager TooManyFunctions:FusedManagerImpl.kt$FusedManagerImpl : IFusedManager TooManyFunctions:FusedManagerRepository.kt$FusedManagerRepository - TooManyFunctions:GPlayHttpClient.kt$GPlayHttpClient : IHttpClient - TooManyFunctions:HomeChildRVAdapter.kt$HomeChildRVAdapter : ListAdapter TooManyFunctions:HomeFragment.kt$HomeFragment : TimeoutFragmentApplicationInstaller TooManyFunctions:IFusedManager.kt$IFusedManager TooManyFunctions:LoginData.kt$LoginData - TooManyFunctions:MainActivity.kt$MainActivity : AppCompatActivity TooManyFunctions:MainActivityViewModel.kt$MainActivityViewModel : ViewModel TooManyFunctions:PkgManagerModule.kt$PkgManagerModule - TooManyFunctions:PlayStoreAuthenticator.kt$PlayStoreAuthenticator : StoreAuthenticatorAuthDataValidator - TooManyFunctions:PlayStoreRepositoryImpl.kt$PlayStoreRepositoryImpl : PlayStoreRepository - TooManyFunctions:RetrofitModule.kt$RetrofitModule TooManyFunctions:SearchFragment.kt$SearchFragment : TimeoutFragmentOnQueryTextListenerOnSuggestionListenerApplicationInstaller TooManyFunctions:TimeoutFragment.kt$TimeoutFragment : Fragment TooManyFunctions:UpdatesFragment.kt$UpdatesFragment : TimeoutFragmentApplicationInstaller - TooManyFunctions:UpdatesManagerImpl.kt$UpdatesManagerImpl - TooManyFunctions:UpdatesWorker.kt$UpdatesWorker : CoroutineWorker UnusedParameter:SearchViewModel.kt$SearchViewModel$lifecycleOwner: LifecycleOwner diff --git a/app/src/main/java/foundation/e/apps/data/application/ApplicationApi.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationApi.kt index 3b47a38dc0803b47e8608837a3fe825a246befeb..e6434f15782ccbff117210a2bb341ffece272e18 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationApi.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationApi.kt @@ -62,71 +62,4 @@ interface ApplicationApi { suspend fun getOSSDownloadInfo(id: String, version: String?): Response - /* - * Function to search cleanapk using package name. - * Will be used to handle f-droid deeplink. - * - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5509 - */ - suspend fun getCleanapkAppDetails(packageName: String): Pair - - suspend fun getApplicationDetails( - packageNameList: List, - authData: AuthData, - origin: Origin - ): Pair, ResultStatus> - - /** - * Filter out apps which are restricted, whose details cannot be fetched. - * If an app is restricted, we do try to fetch the app details inside a - * try-catch block. If that fails, we remove the app, else we keep it even - * if it is restricted. - * - * Popular example: "com.skype.m2" - * - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5174 - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5131 [2] - */ - suspend fun filterRestrictedGPlayApps( - authData: AuthData, - appList: List, - ): ResultSupreme> - - /** - * Get different filter levels. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720 - */ - suspend fun getAppFilterLevel(application: Application, authData: AuthData?): FilterLevel - - /* - * Similar to above method but uses Aurora OSS data class "App". - */ - suspend fun getAppFilterLevel(app: App, authData: AuthData): FilterLevel - - suspend fun getApplicationDetails( - id: String, - packageName: String, - authData: AuthData, - origin: Origin - ): Pair - - /** - * Get fused app installation status. - * Applicable for both native apps and PWAs. - * - * Recommended to use this instead of [PkgManagerModule.getPackageStatus]. - */ - fun getFusedAppInstallationStatus(application: Application): Status - - /** - * @return returns true if there is changes in data, otherwise false - */ - fun isAnyFusedAppUpdated( - newApplications: List, - oldApplications: List - ): Boolean - - fun isAnyAppInstallStatusChanged(currentList: List): Boolean - fun isOpenSourceSelected(): Boolean - } diff --git a/app/src/main/java/foundation/e/apps/data/application/ApplicationApiImpl.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationApiImpl.kt index 60340803d1631cd5da7ed07650b73ea1b1c6da1c..fdd6ab9284d1e45748013aa550bdbe70d5c4c6db 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationApiImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationApiImpl.kt @@ -19,71 +19,52 @@ package foundation.e.apps.data.application import android.content.Context -import android.text.format.Formatter -import com.aurora.gplayapi.Constants import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.App -import com.aurora.gplayapi.data.models.Artwork import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.SearchBundle -import com.aurora.gplayapi.data.models.StreamCluster import dagger.hilt.android.qualifiers.ApplicationContext -import foundation.e.apps.R import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.ApplicationApi.Companion.APP_TYPE_ANY import foundation.e.apps.data.application.ApplicationApi.Companion.APP_TYPE_OPEN import foundation.e.apps.data.application.ApplicationApi.Companion.APP_TYPE_PWA import foundation.e.apps.data.application.data.Application -import foundation.e.apps.data.application.data.Category import foundation.e.apps.data.application.data.Home -import foundation.e.apps.data.application.data.Ratings -import foundation.e.apps.data.application.utils.CategoryType -import foundation.e.apps.data.application.utils.CategoryUtils +import foundation.e.apps.data.application.utils.toApplication import foundation.e.apps.data.cleanapk.CleanApkDownloadInfoFetcher -import foundation.e.apps.data.cleanapk.data.categories.Categories -import foundation.e.apps.data.cleanapk.data.search.Search import foundation.e.apps.data.cleanapk.repositories.CleanApkRepository -import foundation.e.apps.data.enums.AppTag -import foundation.e.apps.data.enums.FilterLevel import foundation.e.apps.data.enums.Origin import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.enums.Type -import foundation.e.apps.data.enums.isUnFiltered import foundation.e.apps.data.fusedDownload.models.FusedDownload import foundation.e.apps.data.handleNetworkResult import foundation.e.apps.data.login.AuthObject import foundation.e.apps.data.playstore.PlayStoreRepository import foundation.e.apps.data.preference.PreferenceManagerModule -import foundation.e.apps.install.pkg.PWAManagerModule -import foundation.e.apps.install.pkg.PkgManagerModule -import foundation.e.apps.ui.applicationlist.ApplicationDiffUtil import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import kotlinx.coroutines.Deferred import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch -import retrofit2.Response import timber.log.Timber import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton -import com.aurora.gplayapi.data.models.Category as GplayapiCategory -import foundation.e.apps.data.cleanapk.data.app.Application as CleanApkApplication typealias FusedHomeDeferred = Deferred>> @Singleton class ApplicationApiImpl @Inject constructor( - private val pkgManagerModule: PkgManagerModule, - private val pwaManagerModule: PWAManagerModule, + private val appsApi: AppsApi, private val preferenceManagerModule: PreferenceManagerModule, @Named("gplayRepository") private val gplayRepository: PlayStoreRepository, @Named("cleanApkAppsRepository") private val cleanApkAppsRepository: CleanApkRepository, @Named("cleanApkPWARepository") private val cleanApkPWARepository: CleanApkRepository, - @ApplicationContext private val context: Context + private val applicationDataManager: ApplicationDataManager ) : ApplicationApi { + @Inject + @ApplicationContext lateinit var context: Context + companion object { private const val KEYWORD_TEST_SEARCH = "facebook" } @@ -108,11 +89,6 @@ class ApplicationApiImpl @Inject constructor( query: String, authData: AuthData ): ResultSupreme, Boolean>> { - /* - * Returning livedata to improve performance, so that we do not have to wait forever - * for all results to be fetched from network before showing them. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 - */ val packageSpecificResults = ArrayList() var finalSearchResult: ResultSupreme, Boolean>> = ResultSupreme.Error() @@ -150,9 +126,9 @@ class ApplicationApiImpl @Inject constructor( val apps = cleanApkPWARepository.getSearchResult(query).body()?.apps apps?.forEach { - it.updateStatus() + applicationDataManager.updateStatus(it) it.updateType() - it.updateSource() + it.updateSource(context) pwaApps.add(it) } } @@ -287,7 +263,7 @@ class ApplicationApiImpl @Inject constructor( authData: AuthData, ): Application? { try { - getApplicationDetails(query, query, authData, Origin.GPLAY).let { + appsApi.getApplicationDetails(query, query, authData, Origin.GPLAY).let { if (it.second == ResultStatus.OK && it.first.package_name.isNotEmpty()) { return it.first } @@ -383,252 +359,6 @@ class ApplicationApiImpl @Inject constructor( override suspend fun getOSSDownloadInfo(id: String, version: String?) = (cleanApkAppsRepository as CleanApkDownloadInfoFetcher).getDownloadInfo(id, version) - /* - * Function to search cleanapk using package name. - * Will be used to handle f-droid deeplink. - * - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5509 - */ - override suspend fun getCleanapkAppDetails(packageName: String): Pair { - var application = Application() - val result = handleNetworkResult { - val result = cleanApkAppsRepository.getSearchResult( - packageName, - "package_name" - ).body() - - if (result?.apps?.isNotEmpty() == true && result.numberOfResults == 1) { - application = - (cleanApkAppsRepository.getAppDetails(result.apps[0]._id) as Response).body()?.app - ?: Application() - } - application.updateFilterLevel(null) - } - return Pair(application, result.getResultStatus()) - } - - // Warning - GPlay results may not have proper geo-restriction information. - override suspend fun getApplicationDetails( - packageNameList: List, - authData: AuthData, - origin: Origin - ): Pair, ResultStatus> { - val list = mutableListOf() - - val response: Pair, ResultStatus> = - if (origin == Origin.CLEANAPK) { - getAppDetailsListFromCleanapk(packageNameList) - } else { - getAppDetailsListFromGPlay(packageNameList, authData) - } - - response.first.forEach { - if (it.package_name.isNotBlank()) { - it.updateStatus() - it.updateType() - list.add(it) - } - } - - return Pair(list, response.second) - } - - /* - * Get app details of a list of apps from cleanapk. - * Returns list of FusedApp and ResultStatus - which will reflect timeout if even one app fails. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5413 - */ - private suspend fun getAppDetailsListFromCleanapk( - packageNameList: List, - ): Pair, ResultStatus> { - var status = ResultStatus.OK - val applicationList = mutableListOf() - - /* - * Fetch result of each cleanapk search with separate timeout, - * i.e. check timeout for individual package query. - */ - for (packageName in packageNameList) { - val result = handleNetworkResult { - cleanApkAppsRepository.getSearchResult( - packageName, - "package_name" - ).body()?.run { - if (apps.isNotEmpty() && numberOfResults == 1) { - applicationList.add( - apps[0].apply { - updateFilterLevel(null) - } - ) - } - } - } - - status = result.getResultStatus() - - /* - * If status is not ok, immediately return. - */ - if (status != ResultStatus.OK) { - return Pair(applicationList, status) - } - } - - return Pair(applicationList, status) - } - - /* - * Get app details of a list of apps from Google Play store. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5413 - */ - private suspend fun getAppDetailsListFromGPlay( - packageNameList: List, - authData: AuthData, - ): Pair, ResultStatus> { - val applicationList = mutableListOf() - - /* - * Old code moved from getApplicationDetails() - */ - val result = handleNetworkResult { - gplayRepository.getAppsDetails(packageNameList).forEach { app -> - /* - * Some apps are restricted to locations. Example "com.skype.m2". - * For restricted apps, check if it is possible to get their specific app info. - * - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5174 - */ - val filter = getAppFilterLevel(app, authData) - if (filter.isUnFiltered()) { - applicationList.add( - app.transformToFusedApp().apply { - filterLevel = filter - } - ) - } - } - } - - return Pair(applicationList, result.getResultStatus()) - } - - /** - * Filter out apps which are restricted, whose details cannot be fetched. - * If an app is restricted, we do try to fetch the app details inside a - * try-catch block. If that fails, we remove the app, else we keep it even - * if it is restricted. - * - * Popular example: "com.skype.m2" - * - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5174 - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5131 [2] - */ - override suspend fun filterRestrictedGPlayApps( - authData: AuthData, - appList: List, - ): ResultSupreme> { - val filteredApplications = mutableListOf() - return handleNetworkResult { - appList.forEach { - val filter = getAppFilterLevel(it, authData) - if (filter.isUnFiltered()) { - filteredApplications.add( - it.transformToFusedApp().apply { - this.filterLevel = filter - } - ) - } - } - filteredApplications - } - } - - /** - * Get different filter levels. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720 - */ - override suspend fun getAppFilterLevel(application: Application, authData: AuthData?): FilterLevel { - return when { - application.package_name.isBlank() -> FilterLevel.UNKNOWN - !application.isFree && application.price.isBlank() -> FilterLevel.UI - application.origin == Origin.CLEANAPK -> FilterLevel.NONE - !isRestricted(application) -> FilterLevel.NONE - authData == null -> FilterLevel.UNKNOWN // cannot determine for gplay app - !isApplicationVisible(application) -> FilterLevel.DATA - application.originalSize == 0L -> FilterLevel.UI - !isDownloadable(application) -> FilterLevel.UI - else -> FilterLevel.NONE - } - } - - /** - * Some apps are simply not visible. - * Example: com.skype.m2 - */ - private suspend fun isApplicationVisible(application: Application): Boolean { - return kotlin.runCatching { gplayRepository.getAppDetails(application.package_name) }.isSuccess - } - - /** - * Some apps are visible but not downloadable. - * Example: com.riotgames.league.wildrift - */ - private suspend fun isDownloadable(application: Application): Boolean { - return kotlin.runCatching { - gplayRepository.getDownloadInfo( - application.package_name, - application.latest_version_code, - application.offer_type, - ) - }.isSuccess - } - - private fun isRestricted(application: Application): Boolean { - return application.restriction != Constants.Restriction.NOT_RESTRICTED - } - - /* - * Similar to above method but uses Aurora OSS data class "App". - */ - override suspend fun getAppFilterLevel(app: App, authData: AuthData): FilterLevel { - return getAppFilterLevel(app.transformToFusedApp(), authData) - } - - /* - * Handy method to run on an instance of FusedApp to update its filter level. - */ - suspend fun Application.updateFilterLevel(authData: AuthData?) { - this.filterLevel = getAppFilterLevel(this, authData) - } - - override suspend fun getApplicationDetails( - id: String, - packageName: String, - authData: AuthData, - origin: Origin - ): Pair { - - var response: Application? = null - - val result = handleNetworkResult { - response = if (origin == Origin.CLEANAPK) { - (cleanApkAppsRepository.getAppDetails(id) as Response).body()?.app - } else { - val app = gplayRepository.getAppDetails(packageName) as App? - app?.transformToFusedApp() - } - response?.let { - it.updateStatus() - it.updateType() - it.updateSource() - it.updateFilterLevel(authData) - } - response - } - - return Pair(result.data ?: Application(), result.getResultStatus()) - } - /* * Search-related internal functions */ @@ -641,9 +371,9 @@ class ApplicationApiImpl @Inject constructor( cleanApkAppsRepository.getSearchResult(keyword).body()?.apps response?.forEach { - it.updateStatus() + applicationDataManager.updateStatus(it) it.updateType() - it.updateSource() + it.updateSource(context) list.add(it) } return list @@ -691,11 +421,11 @@ class ApplicationApiImpl @Inject constructor( * else will show the GPlay app itself. */ private suspend fun replaceWithFDroid(gPlayApp: App): Application { - val gPlayFusedApp = gPlayApp.transformToFusedApp() + val gPlayFusedApp = gPlayApp.toApplication(context) val response = cleanApkAppsRepository.getAppDetails(gPlayApp.packageName) if (response != null) { val fdroidApp = getCleanApkPackageResult(gPlayFusedApp.package_name)?.apply { - updateSource() + this.updateSource(context) isGplayReplaced = true } return fdroidApp ?: gPlayFusedApp @@ -711,120 +441,4 @@ class ApplicationApiImpl @Inject constructor( ) } } - - /* - * FusedApp-related internal extensions and functions - */ - - private fun App.transformToFusedApp(): Application { - val app = Application( - _id = this.id.toString(), - author = this.developerName, - category = this.categoryName, - description = this.description, - perms = this.permissions, - icon_image_path = this.iconArtwork.url, - last_modified = this.updatedOn, - latest_version_code = this.versionCode, - latest_version_number = this.versionName, - name = this.displayName, - other_images_path = this.screenshots.transformToList(), - package_name = this.packageName, - ratings = Ratings( - usageQualityScore = - this.labeledRating.run { - if (isNotEmpty()) { - this.replace(",", ".").toDoubleOrNull() ?: -1.0 - } else -1.0 - } - ), - offer_type = this.offerType, - origin = Origin.GPLAY, - shareUrl = this.shareUrl, - originalSize = this.size, - appSize = Formatter.formatFileSize(context, this.size), - isFree = this.isFree, - price = this.price, - restriction = this.restriction, - ) - app.updateStatus() - return app - } - - /** - * Get fused app installation status. - * Applicable for both native apps and PWAs. - * - * Recommended to use this instead of [PkgManagerModule.getPackageStatus]. - */ - override fun getFusedAppInstallationStatus(application: Application): Status { - return if (application.is_pwa) { - pwaManagerModule.getPwaStatus(application) - } else { - pkgManagerModule.getPackageStatus(application.package_name, application.latest_version_code) - } - } - - private fun Application.updateStatus() { - if (this.status != Status.INSTALLATION_ISSUE) { - this.status = getFusedAppInstallationStatus(this) - } - } - - private fun Application.updateType() { - this.type = if (this.is_pwa) Type.PWA else Type.NATIVE - } - - private fun Application.updateSource() { - this.apply { - source = if (origin == Origin.CLEANAPK && is_pwa) context.getString(R.string.pwa) - else if (origin == Origin.CLEANAPK) context.getString(R.string.open_source) - else "" - } - } - - private fun MutableList.transformToList(): List { - val list = mutableListOf() - this.forEach { - list.add(it.url) - } - return list - } - - /** - * @return returns true if there is changes in data, otherwise false - */ - override fun isAnyFusedAppUpdated( - newApplications: List, - oldApplications: List - ): Boolean { - val fusedAppDiffUtil = ApplicationDiffUtil() - if (newApplications.size != oldApplications.size) { - return true - } - - newApplications.forEach { - val indexOfNewFusedApp = newApplications.indexOf(it) - if (!fusedAppDiffUtil.areContentsTheSame(it, oldApplications[indexOfNewFusedApp])) { - return true - } - } - return false - } - - override fun isAnyAppInstallStatusChanged(currentList: List): Boolean { - currentList.forEach { - if (it.status == Status.INSTALLATION_ISSUE) { - return@forEach - } - val currentAppStatus = - getFusedAppInstallationStatus(it) - if (it.status != currentAppStatus) { - return true - } - } - return false - } - - override fun isOpenSourceSelected() = preferenceManagerModule.isOpenSourceSelected() } diff --git a/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt index 81f5d3d8cedb4e4a879749f9ec813cd346987d0b..24f2ac55a78b9ecda5b147263b3faf209922f531 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt @@ -40,7 +40,8 @@ import javax.inject.Singleton class ApplicationRepository @Inject constructor( private val applicationAPIImpl: ApplicationApi, private val homeApi: HomeApi, - private val categoryApi: CategoryApi + private val categoryApi: CategoryApi, + private val appsApi: AppsApi, ) { suspend fun getHomeScreenData(authData: AuthData): LiveData>> { @@ -56,11 +57,11 @@ class ApplicationRepository @Inject constructor( authData: AuthData, origin: Origin ): Pair, ResultStatus> { - return applicationAPIImpl.getApplicationDetails(packageNameList, authData, origin) + return appsApi.getApplicationDetails(packageNameList, authData, origin) } suspend fun getAppFilterLevel(application: Application, authData: AuthData?): FilterLevel { - return applicationAPIImpl.getAppFilterLevel(application, authData) + return appsApi.getAppFilterLevel(application, authData) } suspend fun getApplicationDetails( @@ -69,11 +70,11 @@ class ApplicationRepository @Inject constructor( authData: AuthData, origin: Origin ): Pair { - return applicationAPIImpl.getApplicationDetails(id, packageName, authData, origin) + return appsApi.getApplicationDetails(id, packageName, authData, origin) } suspend fun getCleanapkAppDetails(packageName: String): Pair { - return applicationAPIImpl.getCleanapkAppDetails(packageName) + return appsApi.getCleanapkAppDetails(packageName) } suspend fun updateFusedDownloadWithDownloadingInfo( @@ -136,16 +137,16 @@ class ApplicationRepository @Inject constructor( } fun getFusedAppInstallationStatus(application: Application): Status { - return applicationAPIImpl.getFusedAppInstallationStatus(application) + return appsApi.getFusedAppInstallationStatus(application) } fun isAnyFusedAppUpdated( newApplications: List, oldApplications: List - ) = applicationAPIImpl.isAnyFusedAppUpdated(newApplications, oldApplications) + ) = appsApi.isAnyFusedAppUpdated(newApplications, oldApplications) fun isAnyAppInstallStatusChanged(currentList: List) = - applicationAPIImpl.isAnyAppInstallStatusChanged(currentList) + appsApi.isAnyAppInstallStatusChanged(currentList) - fun isOpenSourceSelected() = applicationAPIImpl.isOpenSourceSelected() + fun isOpenSourceSelected() = appsApi.isOpenSourceSelected() } diff --git a/app/src/main/java/foundation/e/apps/data/application/AppsApi.kt b/app/src/main/java/foundation/e/apps/data/application/AppsApi.kt new file mode 100644 index 0000000000000000000000000000000000000000..8e3f0491dc30ef4fba674ef84a12d5d411a659d5 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/application/AppsApi.kt @@ -0,0 +1,69 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * 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.apps.data.application + +import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.enums.FilterLevel +import foundation.e.apps.data.enums.Origin +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.enums.Status + +interface AppsApi { + + /* + * Function to search cleanapk using package name. + * Will be used to handle f-droid deeplink. + */ + suspend fun getCleanapkAppDetails(packageName: String): Pair + + suspend fun getApplicationDetails( + packageNameList: List, + authData: AuthData, + origin: Origin + ): Pair, ResultStatus> + + suspend fun getApplicationDetails( + id: String, + packageName: String, + authData: AuthData, + origin: Origin + ): Pair + + /** + * Get fused app installation status. + * Applicable for both native apps and PWAs. + * + * Recommended to use this instead of [PkgManagerModule.getPackageStatus]. + */ + fun getFusedAppInstallationStatus(application: Application): Status + + suspend fun getAppFilterLevel(application: Application, authData: AuthData?): FilterLevel + + /** + * @return returns true if there is changes in data, otherwise false + */ + fun isAnyFusedAppUpdated( + newApplications: List, + oldApplications: List + ): Boolean + + fun isAnyAppInstallStatusChanged(currentList: List): Boolean + fun isOpenSourceSelected(): Boolean +} diff --git a/app/src/main/java/foundation/e/apps/data/application/AppsApiImpl.kt b/app/src/main/java/foundation/e/apps/data/application/AppsApiImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..ea2b781ef88a01d4810a1e0092101e8468ef16b6 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/application/AppsApiImpl.kt @@ -0,0 +1,274 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * 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.apps.data.application + +import android.content.Context +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.cleanapk.data.app.Application as CleanApkApplication +import foundation.e.apps.data.application.utils.toApplication +import foundation.e.apps.data.cleanapk.data.search.Search +import foundation.e.apps.data.cleanapk.repositories.CleanApkRepository +import foundation.e.apps.data.enums.FilterLevel +import foundation.e.apps.data.enums.Origin +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.enums.isUnFiltered +import foundation.e.apps.data.handleNetworkResult +import foundation.e.apps.data.playstore.PlayStoreRepository +import foundation.e.apps.data.preference.PreferenceManagerModule +import foundation.e.apps.ui.applicationlist.ApplicationDiffUtil +import retrofit2.Response +import javax.inject.Inject +import javax.inject.Named + +class AppsApiImpl @Inject constructor( + @ApplicationContext private val context: Context, + private val preferenceManagerModule: PreferenceManagerModule, + @Named("gplayRepository") private val gplayRepository: PlayStoreRepository, + @Named("cleanApkAppsRepository") private val cleanApkAppsRepository: CleanApkRepository, + private val applicationDataManager: ApplicationDataManager +) : AppsApi { + + companion object { + private const val KEY_SEARCH_PACKAGE_NAME = "package_name" + } + + override suspend fun getCleanapkAppDetails(packageName: String): Pair { + var application = Application() + val result = handleNetworkResult { + val result = cleanApkAppsRepository.getSearchResult( + packageName, + KEY_SEARCH_PACKAGE_NAME + ).body() + + if (result?.hasSingleResult() == true) { + application = + (cleanApkAppsRepository.getAppDetails(result.apps[0]._id) as Response) + .body()?.app ?: Application() + } + + application.updateFilterLevel(null) + } + + return Pair(application, result.getResultStatus()) + } + + /* + * Handy method to run on an instance of FusedApp to update its filter level. + */ + private suspend fun Application.updateFilterLevel(authData: AuthData?) { + this.filterLevel = applicationDataManager.getAppFilterLevel(this, authData) + } + + override suspend fun getApplicationDetails( + packageNameList: List, + authData: AuthData, + origin: Origin + ): Pair, ResultStatus> { + val list = mutableListOf() + + val response: Pair, ResultStatus> = + if (origin == Origin.CLEANAPK) { + getAppDetailsListFromCleanApk(packageNameList) + } else { + getAppDetailsListFromGPlay(packageNameList, authData) + } + + response.first.forEach { + if (it.package_name.isNotBlank()) { + applicationDataManager.updateStatus(it) + it.updateType() + list.add(it) + } + } + + return Pair(list, response.second) + } + + /* + * Get app details of a list of apps from cleanapk. + * Returns list of FusedApp and ResultStatus - which will reflect error if even one app fails. + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5413 + */ + private suspend fun getAppDetailsListFromCleanApk( + packageNameList: List, + ): Pair, ResultStatus> { + var status = ResultStatus.OK + val applicationList = mutableListOf() + + for (packageName in packageNameList) { + val result = getCleanApkSearchResultByPackageName(packageName, applicationList) + status = result.getResultStatus() + + if (status != ResultStatus.OK) { + return Pair(applicationList, status) + } + } + + return Pair(applicationList, status) + } + + private suspend fun getAppDetailsListFromGPlay( + packageNameList: List, + authData: AuthData, + ): Pair, ResultStatus> { + val applicationList = mutableListOf() + + val result = handleNetworkResult { + gplayRepository.getAppsDetails(packageNameList).forEach { app -> + handleFilteredApps(app, authData, applicationList) + } + } + + return Pair(applicationList, result.getResultStatus()) + } + + /* + * Some apps are restricted to locations. Example "com.skype.m2". + * For restricted apps, check if it is possible to get their specific app info. + * + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5174 + */ + private suspend fun handleFilteredApps( + app: App, + authData: AuthData, + applicationList: MutableList + ) { + val filter = applicationDataManager.getAppFilterLevel(app.toApplication(context), authData) + if (filter.isUnFiltered()) { + applicationList.add( + app.toApplication(context).apply { + filterLevel = filter + } + ) + } + } + + private suspend fun getCleanApkSearchResultByPackageName( + packageName: String, + applicationList: MutableList + ) = handleNetworkResult { + cleanApkAppsRepository.getSearchResult( + packageName, + KEY_SEARCH_PACKAGE_NAME + ).body()?.run { + handleCleanApkSearch(applicationList) + } + } + + private suspend fun Search.handleCleanApkSearch( + applicationList: MutableList + ) { + if (hasSingleResult()) { + applicationList.add( + apps[0].apply { + updateFilterLevel(null) + } + ) + } + } + + private fun Search.hasSingleResult() = + apps.isNotEmpty() && numberOfResults == 1 + + override suspend fun getApplicationDetails( + id: String, + packageName: String, + authData: AuthData, + origin: Origin + ): Pair { + var application: Application? + + val result = handleNetworkResult { + application = if (origin == Origin.CLEANAPK) { + (cleanApkAppsRepository.getAppDetails(id) as Response).body()?.app + } else { + val app = gplayRepository.getAppDetails(packageName) as App? + app?.toApplication(context) + } + + application?.let { + applicationDataManager.updateStatus(it) + it.updateType() + it.updateSource(context) + it.updateFilterLevel(authData) + } + application + } + + return Pair(result.data ?: Application(), result.getResultStatus()) + } + + override fun getFusedAppInstallationStatus(application: Application): Status { + return applicationDataManager.getFusedAppInstallationStatus(application) + } + + override suspend fun getAppFilterLevel( + application: Application, + authData: AuthData? + ): FilterLevel { + return applicationDataManager.getAppFilterLevel(application, authData) + } + + override fun isAnyFusedAppUpdated( + newApplications: List, + oldApplications: List + ): Boolean { + if (newApplications.size != oldApplications.size) { + return true + } + + return areApplicationsChanged(newApplications, oldApplications) + } + + private fun areApplicationsChanged( + newApplications: List, + oldApplications: List + ): Boolean { + val fusedAppDiffUtil = ApplicationDiffUtil() + newApplications.forEach { + val indexOfNewFusedApp = newApplications.indexOf(it) + if (!fusedAppDiffUtil.areContentsTheSame(it, oldApplications[indexOfNewFusedApp])) { + return true + } + } + + return false + } + + override fun isAnyAppInstallStatusChanged(currentList: List): Boolean { + currentList.forEach { + if (it.status == Status.INSTALLATION_ISSUE) { + return@forEach + } + + val currentAppStatus = getFusedAppInstallationStatus(it) + if (it.status != currentAppStatus) { + return true + } + } + + return false + } + + override fun isOpenSourceSelected() = preferenceManagerModule.isOpenSourceSelected() +} diff --git a/app/src/main/java/foundation/e/apps/data/application/CategoryApiImpl.kt b/app/src/main/java/foundation/e/apps/data/application/CategoryApiImpl.kt index 733f7e07a61acd5eff83350b6d42003ce9165a59..e222d724208b86548498a3374c3809bca461690b 100644 --- a/app/src/main/java/foundation/e/apps/data/application/CategoryApiImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/application/CategoryApiImpl.kt @@ -64,33 +64,27 @@ class CategoryApiImpl @Inject constructor( categoriesList: MutableList, type: CategoryType, ): ResultStatus { - var categoryResult: ResultStatus = ResultStatus.OK - - /** Here, `categoryResult` is updated by fetchCategoryResult() - And `fetchCategoryResult()` returns the result of categoryResult based - on current categoryResu lt, if currentCategoryResult != ResultStatus.OK, - fetchCategoryResult() doesn't return the latest result to preserve the error.*/ + val categoryResults: MutableList = mutableListOf() if (preferenceManagerModule.isOpenSourceSelected()) { - categoryResult = fetchCategoryResult(categoriesList, type, Source.OPEN, categoryResult) + categoryResults.add(fetchCategoryResult(categoriesList, type, Source.OPEN)) } if (preferenceManagerModule.isPWASelected()) { - categoryResult = fetchCategoryResult(categoriesList, type, Source.PWA, categoryResult) + categoryResults.add(fetchCategoryResult(categoriesList, type, Source.PWA)) } if (preferenceManagerModule.isGplaySelected()) { - categoryResult = fetchCategoryResult(categoriesList, type, Source.GPLAY, categoryResult) + categoryResults.add(fetchCategoryResult(categoriesList, type, Source.GPLAY)) } - return categoryResult + return categoryResults.find { it != ResultStatus.OK } ?: ResultStatus.OK } private suspend fun fetchCategoryResult( categoriesList: MutableList, type: CategoryType, - source: Source, - currentCategoryResult: ResultStatus + source: Source ): ResultStatus { val categoryResult = when (source) { Source.OPEN -> { @@ -110,8 +104,7 @@ class CategoryApiImpl @Inject constructor( categoriesList.addAll(it.first) } - return if (currentCategoryResult == ResultStatus.OK) - categoryResult.second else currentCategoryResult + return categoryResult.second } private suspend fun fetchGplayCategories( diff --git a/app/src/main/java/foundation/e/apps/data/application/data/Application.kt b/app/src/main/java/foundation/e/apps/data/application/data/Application.kt index 7b42d7e71ae69723d3e0c22d362c60dc54286d5b..ba7836bcc60e1eb561b73113c21e03837dee9f25 100644 --- a/app/src/main/java/foundation/e/apps/data/application/data/Application.kt +++ b/app/src/main/java/foundation/e/apps/data/application/data/Application.kt @@ -18,7 +18,9 @@ package foundation.e.apps.data.application.data +import android.content.Context import com.aurora.gplayapi.Constants.Restriction +import foundation.e.apps.R import foundation.e.apps.data.enums.FilterLevel import foundation.e.apps.data.enums.Origin import foundation.e.apps.data.enums.Status @@ -97,4 +99,12 @@ data class Application( fun updateType() { this.type = if (this.is_pwa) Type.PWA else Type.NATIVE } + + fun updateSource(context: Context) { + this.apply { + source = if (origin != Origin.CLEANAPK) "" + else if (is_pwa) context.getString(R.string.pwa) + else context.getString(R.string.open_source) + } + } } diff --git a/app/src/main/java/foundation/e/apps/data/application/utils/GplayApiExtensions.kt b/app/src/main/java/foundation/e/apps/data/application/utils/GplayApiExtensions.kt index 404c7384d2dd013ef0f7f60645d49d904a8096b8..be26c5669b9ad838ef0353104bd290fb94fb07e2 100644 --- a/app/src/main/java/foundation/e/apps/data/application/utils/GplayApiExtensions.kt +++ b/app/src/main/java/foundation/e/apps/data/application/utils/GplayApiExtensions.kt @@ -23,7 +23,7 @@ import android.text.format.Formatter import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Artwork import com.aurora.gplayapi.data.models.Category -import foundation.e.apps.data.application.data.Category as AppLoungeCategroy +import foundation.e.apps.data.application.data.Category as AppLoungeCategory import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.application.data.Ratings import foundation.e.apps.data.enums.Origin @@ -62,9 +62,9 @@ fun App.toApplication(context: Context): Application { return app } -fun Category.toCategory(): AppLoungeCategroy { +fun Category.toCategory(): AppLoungeCategory { val id = this.browseUrl.substringAfter("cat=").substringBefore("&c=") - return AppLoungeCategroy( + return AppLoungeCategory( id = id.lowercase(), title = this.title, browseUrl = this.browseUrl, diff --git a/app/src/main/java/foundation/e/apps/di/DataModule.kt b/app/src/main/java/foundation/e/apps/di/DataModule.kt index 0dfccd751157fd2a882f63434d2da2391bbb960f..0d51d6e5796e898ff418234d026a0cdfe38558cf 100644 --- a/app/src/main/java/foundation/e/apps/di/DataModule.kt +++ b/app/src/main/java/foundation/e/apps/di/DataModule.kt @@ -22,6 +22,8 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import foundation.e.apps.data.application.AppsApi +import foundation.e.apps.data.application.AppsApiImpl import foundation.e.apps.data.application.CategoryApi import foundation.e.apps.data.application.CategoryApiImpl import foundation.e.apps.data.application.HomeApi @@ -39,4 +41,8 @@ interface DataModule { @Singleton @Binds fun getCategoryApi(categoryApiImpl: CategoryApiImpl): CategoryApi + + @Singleton + @Binds + fun getAppsApi(appsApiImpl: AppsApiImpl): AppsApi } diff --git a/app/src/test/java/foundation/e/apps/apps/AppsApiTest.kt b/app/src/test/java/foundation/e/apps/apps/AppsApiTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..ff72d8c191201a194f6d70bb20a2b44875eceea1 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/apps/AppsApiTest.kt @@ -0,0 +1,489 @@ +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * 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.apps.apps + +import android.content.Context +import android.text.format.Formatter +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.aurora.gplayapi.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.FakePreferenceModule +import foundation.e.apps.data.cleanapk.repositories.CleanApkRepository +import foundation.e.apps.data.enums.FilterLevel +import foundation.e.apps.data.enums.Origin +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.application.ApplicationDataManager +import foundation.e.apps.data.application.AppsApi +import foundation.e.apps.data.application.AppsApiImpl +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.playstore.PlayStoreRepository +import foundation.e.apps.install.pkg.PWAManagerModule +import foundation.e.apps.install.pkg.PkgManagerModule +import foundation.e.apps.util.MainCoroutineRule +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockedStatic +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.eq + +@OptIn(ExperimentalCoroutinesApi::class) +class AppsApiTest { + + // Run tasks synchronously + @Rule + @JvmField + val instantExecutorRule = InstantTaskExecutorRule() + + // Sets the main coroutines dispatcher to a TestCoroutineScope for unit testing. + @ExperimentalCoroutinesApi + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + + @Mock + private lateinit var pwaManagerModule: PWAManagerModule + + @Mock + private lateinit var pkgManagerModule: PkgManagerModule + + @Mock + private lateinit var context: Context + + @Mock + private lateinit var cleanApkAppsRepository: CleanApkRepository + + @Mock + private lateinit var gPlayAPIRepository: PlayStoreRepository + + private lateinit var appsApi: AppsApi + + private lateinit var applicationDataManager: ApplicationDataManager + + private lateinit var preferenceManagerModule: FakePreferenceModule + + private lateinit var formatterMocked: MockedStatic + + companion object { + private val AUTH_DATA = AuthData("e@e.email", "AtadyMsIAtadyM") + } + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + formatterMocked = Mockito.mockStatic(Formatter::class.java) + preferenceManagerModule = FakePreferenceModule(context) + applicationDataManager = + ApplicationDataManager(gPlayAPIRepository, pkgManagerModule, pwaManagerModule) + + appsApi = AppsApiImpl( + context, + preferenceManagerModule, + gPlayAPIRepository, + cleanApkAppsRepository, + applicationDataManager, + ) + } + + @After + fun after() { + formatterMocked.close() + } + + @Test + fun `is any app updated when new list is empty`() { + val oldAppList = mutableListOf( + Application( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone" + ), + Application( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo" + ), + Application( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "foundation.e.demothree" + ) + ) + + val newAppList = mutableListOf() + val isFusedAppUpdated = appsApi.isAnyFusedAppUpdated(newAppList, oldAppList) + assertTrue("isAnyAppUpdated", isFusedAppUpdated) + } + + @Test + fun `is any app updated when both list are empty`() { + val isFusedAppUpdated = appsApi.isAnyFusedAppUpdated(listOf(), listOf()) + assertFalse("isAnyAppUpdated", isFusedAppUpdated) + } + + @Test + fun `is any app updated when any app is uninstalled`() { + val oldAppList = mutableListOf( + Application( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone" + ), + Application( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo" + ), + Application( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "foundation.e.demothree" + ) + ) + + val newAppList = mutableListOf( + Application( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone" + ), + Application( + _id = "112", + status = Status.UNAVAILABLE, + name = "Demo Two", + package_name = "foundation.e.demotwo" + ), + Application( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "foundation.e.demothree" + ) + ) + + val isFusedAppUpdated = appsApi.isAnyFusedAppUpdated(newAppList, oldAppList) + assertTrue("isAnyFusedAppUpdated", isFusedAppUpdated) + } + + @Test + fun `has any app install status changed when changed`() { + val oldAppList = mutableListOf( + Application( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone", + latest_version_code = 123 + ), + Application( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo", + latest_version_code = 123 + ), + Application( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "foundation.e.demothree", + latest_version_code = 123 + ) + ) + + Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demoone"), eq(123))) + .thenReturn( + Status.UNAVAILABLE + ) + Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demotwo"), eq(123))) + .thenReturn( + Status.UNAVAILABLE + ) + Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demothree"), eq(123))) + .thenReturn( + Status.UNAVAILABLE + ) + + val isAppStatusUpdated = appsApi.isAnyAppInstallStatusChanged(oldAppList) + assertTrue("hasInstallStatusUpdated", isAppStatusUpdated) + } + + @Test + fun `has any app install status changed when not changed`() { + val oldAppList = mutableListOf( + Application( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone", + latest_version_code = 123 + ), + Application( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo", + latest_version_code = 123 + ), + Application( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "foundation.e.demothree", + latest_version_code = 123 + ) + ) + + Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demoone"), eq(123))) + .thenReturn( + Status.UNAVAILABLE + ) + Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demotwo"), eq(123))) + .thenReturn( + Status.INSTALLED + ) + Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demothree"), eq(123))) + .thenReturn( + Status.UNAVAILABLE + ) + + val isAppStatusUpdated = appsApi.isAnyAppInstallStatusChanged(oldAppList) + assertFalse("hasInstallStatusUpdated", isAppStatusUpdated) + } + + @Test + fun `has any app install status changed when installation_issue`() { + val oldAppList = mutableListOf( + Application( + _id = "111", + status = Status.INSTALLATION_ISSUE, + name = "Demo One", + package_name = "foundation.e.demoone", + latest_version_code = 123 + ), + Application( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo", + latest_version_code = 123 + ), + Application( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "foundation.e.demothree", + latest_version_code = 123 + ) + ) + + Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demoone"), eq(123))) + .thenReturn( + Status.UNAVAILABLE + ) + Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demotwo"), eq(123))) + .thenReturn( + Status.INSTALLED + ) + Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demothree"), eq(123))) + .thenReturn( + Status.UNAVAILABLE + ) + + val isAppStatusUpdated = appsApi.isAnyAppInstallStatusChanged(oldAppList) + assertFalse("hasInstallStatusUpdated", isAppStatusUpdated) + } + + + @Test + fun getFusedAppInstallationStatusWhenPWA() { + val application = Application( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "foundation.e.demothree", + latest_version_code = 123, + is_pwa = true + ) + + Mockito.`when`(pwaManagerModule.getPwaStatus(application)).thenReturn(application.status) + + val installationStatus = appsApi.getFusedAppInstallationStatus(application) + assertEquals("getFusedAppInstallationStatusWhenPWA", application.status, installationStatus) + } + + @Test + fun getFusedAppInstallationStatus() { + val application = Application( + _id = "113", + name = "Demo Three", + package_name = "foundation.e.demothree", + latest_version_code = 123, + ) + + Mockito.`when`( + pkgManagerModule.getPackageStatus( + application.package_name, application.latest_version_code + ) + ).thenReturn(Status.INSTALLED) + + val installationStatus = appsApi.getFusedAppInstallationStatus(application) + assertEquals("getFusedAppInstallationStatusWhenPWA", Status.INSTALLED, installationStatus) + } + + @Test + fun `getAppFilterLevel when package name is empty`() = runTest { + val application = Application( + _id = "113", + name = "Demo Three", + package_name = "", + latest_version_code = 123, + ) + + val filterLevel = appsApi.getAppFilterLevel(application, AUTH_DATA) + assertEquals("getAppFilterLevel", FilterLevel.UNKNOWN, filterLevel) + } + + @Test + fun `getAppFilterLevel when app is CleanApk`() = runTest { + val fusedApp = getFusedAppForFilterLevelTest() + + val filterLevel = appsApi.getAppFilterLevel(fusedApp, AUTH_DATA) + assertEquals("getAppFilterLevel", FilterLevel.NONE, filterLevel) + } + + private fun getFusedAppForFilterLevelTest(isFree: Boolean = true) = Application( + _id = "113", + name = "Demo Three", + package_name = "foundation.e.demothree", + latest_version_code = 123, + origin = Origin.CLEANAPK, + originalSize = -1, + isFree = isFree, + price = "" + ) + + @Test + fun `getAppFilterLevel when Authdata is NULL`() = runTest { + val fusedApp = getFusedAppForFilterLevelTest() + + val filterLevel = appsApi.getAppFilterLevel(fusedApp, null) + assertEquals("getAppFilterLevel", FilterLevel.NONE, filterLevel) + } + + @Test + fun `getAppFilterLevel when app is restricted and paid and no price`() = runTest { + val fusedApp = getFusedAppForFilterLevelTest(false).apply { + this.origin = Origin.GPLAY + this.restriction = Constants.Restriction.UNKNOWN + } + + val filterLevel = appsApi.getAppFilterLevel(fusedApp, AUTH_DATA) + assertEquals("getAppFilterLevel", FilterLevel.UI, filterLevel) + } + + @Test + fun `getAppFilterLevel when app is not_restricted and paid and no price`() = runTest { + val fusedApp = getFusedAppForFilterLevelTest(false).apply { + this.origin = Origin.GPLAY + this.restriction = Constants.Restriction.NOT_RESTRICTED + } + + val filterLevel = appsApi.getAppFilterLevel(fusedApp, AUTH_DATA) + assertEquals("getAppFilterLevel", FilterLevel.UI, filterLevel) + } + + @Test + fun `getAppFilterLevel when app is restricted and getAppDetails and getDownloadDetails returns success`() = + runTest { + val fusedApp = getFusedAppForFilterLevelTest().apply { + this.origin = Origin.GPLAY + this.restriction = Constants.Restriction.UNKNOWN + } + + Mockito.`when`(gPlayAPIRepository.getAppDetails(fusedApp.package_name)) + .thenReturn(App(fusedApp.package_name)) + + Mockito.`when`( + gPlayAPIRepository.getDownloadInfo( + fusedApp.package_name, + fusedApp.latest_version_code, + fusedApp.offer_type, + ) + ).thenReturn(listOf()) + + val filterLevel = appsApi.getAppFilterLevel(fusedApp, AUTH_DATA) + assertEquals("getAppFilterLevel", FilterLevel.NONE, filterLevel) + } + + @Test + fun `getAppFilterLevel when app is restricted and getAppDetails throws exception`() = runTest { + val fusedApp = getFusedAppForFilterLevelTest().apply { + this.origin = Origin.GPLAY + this.restriction = Constants.Restriction.UNKNOWN + } + + Mockito.`when`(gPlayAPIRepository.getAppDetails(fusedApp.package_name)) + .thenThrow(RuntimeException()) + + Mockito.`when`( + gPlayAPIRepository.getDownloadInfo( + fusedApp.package_name, fusedApp.latest_version_code, fusedApp.offer_type + ) + ).thenReturn(listOf()) + + val filterLevel = appsApi.getAppFilterLevel(fusedApp, AUTH_DATA) + assertEquals("getAppFilterLevel", FilterLevel.DATA, filterLevel) + } + + @Test + fun `getAppFilterLevel when app is restricted and getDownoadInfo throws exception`() = runTest { + val fusedApp = getFusedAppForFilterLevelTest().apply { + this.origin = Origin.GPLAY + this.restriction = Constants.Restriction.UNKNOWN + } + + Mockito.`when`(gPlayAPIRepository.getAppDetails(fusedApp.package_name)) + .thenReturn(App(fusedApp.package_name)) + + Mockito.`when`( + gPlayAPIRepository.getDownloadInfo( + fusedApp.package_name, fusedApp.latest_version_code, fusedApp.offer_type + ) + ).thenThrow(RuntimeException()) + + val filterLevel = appsApi.getAppFilterLevel(fusedApp, AUTH_DATA) + assertEquals("getAppFilterLevel", FilterLevel.UI, filterLevel) + } +} diff --git a/app/src/test/java/foundation/e/apps/fused/ApplicationApiImplTest.kt b/app/src/test/java/foundation/e/apps/fused/ApplicationApiImplTest.kt index 39092ecef5963622cd2f62a1b6ee9d040ffd12d6..b29ece50a07ea52340507dbc5c7c1ae11dd0510b 100644 --- a/app/src/test/java/foundation/e/apps/fused/ApplicationApiImplTest.kt +++ b/app/src/test/java/foundation/e/apps/fused/ApplicationApiImplTest.kt @@ -35,6 +35,9 @@ import foundation.e.apps.data.enums.Origin import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.Status import foundation.e.apps.data.application.ApplicationApiImpl +import foundation.e.apps.data.application.ApplicationDataManager +import foundation.e.apps.data.application.AppsApi +import foundation.e.apps.data.application.AppsApiImpl import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.application.utils.CategoryType import foundation.e.apps.data.playstore.PlayStoreRepository @@ -99,6 +102,10 @@ class ApplicationApiImplTest { @Mock private lateinit var gPlayAPIRepository: PlayStoreRepository + private lateinit var appsApi: AppsApi + + private lateinit var applicationDataManager: ApplicationDataManager + private lateinit var preferenceManagerModule: FakePreferenceModule private lateinit var formatterMocked: MockedStatic @@ -112,14 +119,24 @@ class ApplicationApiImplTest { MockitoAnnotations.openMocks(this) formatterMocked = Mockito.mockStatic(Formatter::class.java) preferenceManagerModule = FakePreferenceModule(context) + applicationDataManager = + ApplicationDataManager(gPlayAPIRepository, pkgManagerModule, pwaManagerModule) + + appsApi = AppsApiImpl( + context, + preferenceManagerModule, + gPlayAPIRepository, + cleanApkAppsRepository, + applicationDataManager, + ) + fusedAPIImpl = ApplicationApiImpl( - pkgManagerModule, - pwaManagerModule, + appsApi, preferenceManagerModule, gPlayAPIRepository, cleanApkAppsRepository, cleanApkPWARepository, - context + applicationDataManager ) } @@ -128,275 +145,6 @@ class ApplicationApiImplTest { formatterMocked.close() } - @Test - fun `is any app updated when new list is empty`() { - val oldAppList = mutableListOf( - Application( - _id = "111", - status = Status.UNAVAILABLE, - name = "Demo One", - package_name = "foundation.e.demoone" - ), - Application( - _id = "112", - status = Status.INSTALLED, - name = "Demo Two", - package_name = "foundation.e.demotwo" - ), - Application( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "foundation.e.demothree" - ) - ) - - val newAppList = mutableListOf() - val isFusedAppUpdated = fusedAPIImpl.isAnyFusedAppUpdated(newAppList, oldAppList) - assertTrue("isAnyAppUpdated", isFusedAppUpdated) - } - - @Test - fun `is any app updated when both list are empty`() { - val isFusedAppUpdated = fusedAPIImpl.isAnyFusedAppUpdated(listOf(), listOf()) - assertFalse("isAnyAppUpdated", isFusedAppUpdated) - } - - @Test - fun `is any app updated when any app is uninstalled`() { - val oldAppList = mutableListOf( - Application( - _id = "111", - status = Status.UNAVAILABLE, - name = "Demo One", - package_name = "foundation.e.demoone" - ), - Application( - _id = "112", - status = Status.INSTALLED, - name = "Demo Two", - package_name = "foundation.e.demotwo" - ), - Application( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "foundation.e.demothree" - ) - ) - - val newAppList = mutableListOf( - Application( - _id = "111", - status = Status.UNAVAILABLE, - name = "Demo One", - package_name = "foundation.e.demoone" - ), - Application( - _id = "112", - status = Status.UNAVAILABLE, - name = "Demo Two", - package_name = "foundation.e.demotwo" - ), - Application( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "foundation.e.demothree" - ) - ) - - val isFusedAppUpdated = fusedAPIImpl.isAnyFusedAppUpdated(newAppList, oldAppList) - assertTrue("isAnyFusedAppUpdated", isFusedAppUpdated) - } - - @Test - fun `has any app install status changed when changed`() { - val oldAppList = mutableListOf( - Application( - _id = "111", - status = Status.UNAVAILABLE, - name = "Demo One", - package_name = "foundation.e.demoone", - latest_version_code = 123 - ), - Application( - _id = "112", - status = Status.INSTALLED, - name = "Demo Two", - package_name = "foundation.e.demotwo", - latest_version_code = 123 - ), - Application( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "foundation.e.demothree", - latest_version_code = 123 - ) - ) - - Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demoone"), eq(123))) - .thenReturn( - Status.UNAVAILABLE - ) - Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demotwo"), eq(123))) - .thenReturn( - Status.UNAVAILABLE - ) - Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demothree"), eq(123))) - .thenReturn( - Status.UNAVAILABLE - ) - - val isAppStatusUpdated = fusedAPIImpl.isAnyAppInstallStatusChanged(oldAppList) - assertTrue("hasInstallStatusUpdated", isAppStatusUpdated) - } - - @Test - fun `has any app install status changed when not changed`() { - val oldAppList = mutableListOf( - Application( - _id = "111", - status = Status.UNAVAILABLE, - name = "Demo One", - package_name = "foundation.e.demoone", - latest_version_code = 123 - ), - Application( - _id = "112", - status = Status.INSTALLED, - name = "Demo Two", - package_name = "foundation.e.demotwo", - latest_version_code = 123 - ), - Application( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "foundation.e.demothree", - latest_version_code = 123 - ) - ) - - Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demoone"), eq(123))) - .thenReturn( - Status.UNAVAILABLE - ) - Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demotwo"), eq(123))) - .thenReturn( - Status.INSTALLED - ) - Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demothree"), eq(123))) - .thenReturn( - Status.UNAVAILABLE - ) - - val isAppStatusUpdated = fusedAPIImpl.isAnyAppInstallStatusChanged(oldAppList) - assertFalse("hasInstallStatusUpdated", isAppStatusUpdated) - } - - @Test - fun `has any app install status changed when installation_issue`() { - val oldAppList = mutableListOf( - Application( - _id = "111", - status = Status.INSTALLATION_ISSUE, - name = "Demo One", - package_name = "foundation.e.demoone", - latest_version_code = 123 - ), - Application( - _id = "112", - status = Status.INSTALLED, - name = "Demo Two", - package_name = "foundation.e.demotwo", - latest_version_code = 123 - ), - Application( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "foundation.e.demothree", - latest_version_code = 123 - ) - ) - - Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demoone"), eq(123))) - .thenReturn( - Status.UNAVAILABLE - ) - Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demotwo"), eq(123))) - .thenReturn( - Status.INSTALLED - ) - Mockito.`when`(pkgManagerModule.getPackageStatus(eq("foundation.e.demothree"), eq(123))) - .thenReturn( - Status.UNAVAILABLE - ) - - val isAppStatusUpdated = fusedAPIImpl.isAnyAppInstallStatusChanged(oldAppList) - assertFalse("hasInstallStatusUpdated", isAppStatusUpdated) - } - - - @Test - fun getFusedAppInstallationStatusWhenPWA() { - val application = Application( - _id = "113", - status = Status.UNAVAILABLE, - name = "Demo Three", - package_name = "foundation.e.demothree", - latest_version_code = 123, - is_pwa = true - ) - - Mockito.`when`(pwaManagerModule.getPwaStatus(application)).thenReturn(application.status) - - val installationStatus = fusedAPIImpl.getFusedAppInstallationStatus(application) - assertEquals("getFusedAppInstallationStatusWhenPWA", application.status, installationStatus) - } - - @Test - fun getFusedAppInstallationStatus() { - val application = Application( - _id = "113", - name = "Demo Three", - package_name = "foundation.e.demothree", - latest_version_code = 123, - ) - - Mockito.`when`( - pkgManagerModule.getPackageStatus( - application.package_name, application.latest_version_code - ) - ).thenReturn(Status.INSTALLED) - - val installationStatus = fusedAPIImpl.getFusedAppInstallationStatus(application) - assertEquals("getFusedAppInstallationStatusWhenPWA", Status.INSTALLED, installationStatus) - } - - @Test - fun `getAppFilterLevel when package name is empty`() = runTest { - val application = Application( - _id = "113", - name = "Demo Three", - package_name = "", - latest_version_code = 123, - ) - - val filterLevel = fusedAPIImpl.getAppFilterLevel(application, AUTH_DATA) - assertEquals("getAppFilterLevel", FilterLevel.UNKNOWN, filterLevel) - } - - @Test - fun `getAppFilterLevel when app is CleanApk`() = runTest { - val fusedApp = getFusedAppForFilterLevelTest() - - val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, AUTH_DATA) - assertEquals("getAppFilterLevel", FilterLevel.NONE, filterLevel) - } - private fun getFusedAppForFilterLevelTest(isFree: Boolean = true) = Application( _id = "113", name = "Demo Three", @@ -408,99 +156,6 @@ class ApplicationApiImplTest { price = "" ) - @Test - fun `getAppFilterLevel when Authdata is NULL`() = runTest { - val fusedApp = getFusedAppForFilterLevelTest() - - val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, null) - assertEquals("getAppFilterLevel", FilterLevel.NONE, filterLevel) - } - - @Test - fun `getAppFilterLevel when app is restricted and paid and no price`() = runTest { - val fusedApp = getFusedAppForFilterLevelTest(false).apply { - this.origin = Origin.GPLAY - this.restriction = Constants.Restriction.UNKNOWN - } - - val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, AUTH_DATA) - assertEquals("getAppFilterLevel", FilterLevel.UI, filterLevel) - } - - @Test - fun `getAppFilterLevel when app is not_restricted and paid and no price`() = runTest { - val fusedApp = getFusedAppForFilterLevelTest(false).apply { - this.origin = Origin.GPLAY - this.restriction = Constants.Restriction.NOT_RESTRICTED - } - - val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, AUTH_DATA) - assertEquals("getAppFilterLevel", FilterLevel.UI, filterLevel) - } - - @Test - fun `getAppFilterLevel when app is restricted and getAppDetails and getDownloadDetails returns success`() = - runTest { - val fusedApp = getFusedAppForFilterLevelTest().apply { - this.origin = Origin.GPLAY - this.restriction = Constants.Restriction.UNKNOWN - } - - Mockito.`when`(gPlayAPIRepository.getAppDetails(fusedApp.package_name)) - .thenReturn(App(fusedApp.package_name)) - - Mockito.`when`( - gPlayAPIRepository.getDownloadInfo( - fusedApp.package_name, - fusedApp.latest_version_code, - fusedApp.offer_type, - ) - ).thenReturn(listOf()) - - val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, AUTH_DATA) - assertEquals("getAppFilterLevel", FilterLevel.NONE, filterLevel) - } - - @Test - fun `getAppFilterLevel when app is restricted and getAppDetails throws exception`() = runTest { - val fusedApp = getFusedAppForFilterLevelTest().apply { - this.origin = Origin.GPLAY - this.restriction = Constants.Restriction.UNKNOWN - } - - Mockito.`when`(gPlayAPIRepository.getAppDetails(fusedApp.package_name)) - .thenThrow(RuntimeException()) - - Mockito.`when`( - gPlayAPIRepository.getDownloadInfo( - fusedApp.package_name, fusedApp.latest_version_code, fusedApp.offer_type - ) - ).thenReturn(listOf()) - - val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, AUTH_DATA) - assertEquals("getAppFilterLevel", FilterLevel.DATA, filterLevel) - } - - @Test - fun `getAppFilterLevel when app is restricted and getDownoadInfo throws exception`() = runTest { - val fusedApp = getFusedAppForFilterLevelTest().apply { - this.origin = Origin.GPLAY - this.restriction = Constants.Restriction.UNKNOWN - } - - Mockito.`when`(gPlayAPIRepository.getAppDetails(fusedApp.package_name)) - .thenReturn(App(fusedApp.package_name)) - - Mockito.`when`( - gPlayAPIRepository.getDownloadInfo( - fusedApp.package_name, fusedApp.latest_version_code, fusedApp.offer_type - ) - ).thenThrow(RuntimeException()) - - val filterLevel = fusedAPIImpl.getAppFilterLevel(fusedApp, AUTH_DATA) - assertEquals("getAppFilterLevel", FilterLevel.UI, filterLevel) - } - @Ignore("Dependencies are not mockable") @Test fun `getSearchResult When all sources are selected`() = runTest { @@ -647,7 +302,8 @@ class ApplicationApiImplTest { fun testSearchResultWhenDataIsLimited() = runTest { preferenceManagerModule.isGplaySelectedFake = true formatterMocked.`when` { Formatter.formatFileSize(any(), any()) }.thenReturn("15MB") - Mockito.`when`(gPlayAPIRepository.getSearchResult(anyString(), eq(null))).thenReturn(Pair(emptyList(), mutableSetOf())) + Mockito.`when`(gPlayAPIRepository.getSearchResult(anyString(), eq(null))) + .thenReturn(Pair(emptyList(), mutableSetOf())) Mockito.`when`(cleanApkAppsRepository.getAppDetails(any())).thenReturn(null) var isEventBusTriggered = false diff --git a/app/src/test/java/foundation/e/apps/fused/ApplicationApiRepositoryTest.kt b/app/src/test/java/foundation/e/apps/fused/ApplicationApiRepositoryTest.kt index 85fa1333bc5ed4292ca45156d8f733196c505306..3d59ee61d1d5155c81f6e115493a1b23a63af9a8 100644 --- a/app/src/test/java/foundation/e/apps/fused/ApplicationApiRepositoryTest.kt +++ b/app/src/test/java/foundation/e/apps/fused/ApplicationApiRepositoryTest.kt @@ -19,6 +19,7 @@ package foundation.e.apps.fused import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.application.ApplicationApiImpl +import foundation.e.apps.data.application.AppsApi import foundation.e.apps.data.application.CategoryApi import foundation.e.apps.data.application.HomeApi import org.junit.Assert.assertTrue @@ -33,27 +34,32 @@ class ApplicationApiRepositoryTest { private lateinit var applicationRepository: ApplicationRepository @Mock private lateinit var fusedAPIImpl: ApplicationApiImpl + @Mock private lateinit var homeApi: HomeApi + @Mock private lateinit var categoryApi: CategoryApi + @Mock + private lateinit var appsApi: AppsApi + @Before fun setup() { MockitoAnnotations.openMocks(this) - applicationRepository = ApplicationRepository(fusedAPIImpl, homeApi, categoryApi) + applicationRepository = ApplicationRepository(fusedAPIImpl, homeApi, categoryApi, appsApi) } @Test fun isAnyAppUpdated_ReturnsTrue() { - Mockito.`when`(fusedAPIImpl.isAnyFusedAppUpdated(any(), any())).thenReturn(true) + Mockito.`when`(appsApi.isAnyFusedAppUpdated(any(), any())).thenReturn(true) val isAnyAppUpdated = applicationRepository.isAnyFusedAppUpdated(listOf(), listOf()) assertTrue("isAnyAppUpdated", isAnyAppUpdated) } @Test fun isAnyInstallStatusChanged_ReturnsTrue() { - Mockito.`when`(fusedAPIImpl.isAnyAppInstallStatusChanged(any())).thenReturn(true) + Mockito.`when`(appsApi.isAnyAppInstallStatusChanged(any())).thenReturn(true) val isAnyAppUpdated = applicationRepository.isAnyAppInstallStatusChanged(listOf()) assertTrue("isAnyAppUpdated", isAnyAppUpdated) } diff --git a/detekt.yml b/detekt.yml index 4f6148616b2b1866640658a943d951c6bebe75fc..773174aa03128a8507e97a7662dc88e59963a973 100644 --- a/detekt.yml +++ b/detekt.yml @@ -13,3 +13,9 @@ style: ForbiddenComment: active: false + +# Complexity rules +complexity: + + TooManyFunctions: + ignorePrivate: true