From 3d299a6af17b7c8e930d99a0ccec5565e3fe9a29 Mon Sep 17 00:00:00 2001 From: hasibprince Date: Fri, 15 Jul 2022 16:14:33 +0600 Subject: [PATCH 1/5] boradcasted change of app status --- .../e/apps/manager/pkg/PkgManagerBR.kt | 13 ++++++---- .../e/apps/utils/eventBus/AppEvent.kt | 1 + .../utils/parentFragment/TimeoutFragment.kt | 24 +++++++++++++++++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt index 665150bdd..7f915a8bc 100644 --- a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt +++ b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt @@ -26,10 +26,9 @@ import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.api.faultyApps.FaultyAppRepository import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.utils.enums.Status -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import foundation.e.apps.utils.eventBus.AppEvent +import foundation.e.apps.utils.eventBus.EventBus +import kotlinx.coroutines.* import timber.log.Timber import javax.inject.Inject import javax.inject.Named @@ -68,10 +67,16 @@ open class PkgManagerBR : BroadcastReceiver() { Intent.ACTION_PACKAGE_ADDED -> { updateDownloadStatus(pkgName) removeFaultyAppByPackageName(pkgName) + MainScope().launch { + EventBus.invokeEvent(AppEvent.AppStatusUpdated()) + } } Intent.ACTION_PACKAGE_REMOVED -> { if (!isUpdating) deleteDownload(pkgName) removeFaultyAppByPackageName(pkgName) + MainScope().launch { + EventBus.invokeEvent(AppEvent.AppStatusUpdated()) + } } PkgManagerModule.ERROR_PACKAGE_INSTALL -> { Timber.e("Installation failed due to error: $extra") diff --git a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt index a3a7306ef..29844642a 100644 --- a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt +++ b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt @@ -22,4 +22,5 @@ package foundation.e.apps.utils.eventBus sealed class AppEvent(val data: Any) { class SignatureMissMatchError(packageName: String) : AppEvent(packageName) + class AppStatusUpdated: AppEvent("") } diff --git a/app/src/main/java/foundation/e/apps/utils/parentFragment/TimeoutFragment.kt b/app/src/main/java/foundation/e/apps/utils/parentFragment/TimeoutFragment.kt index 959dc2efb..3926b7f2a 100644 --- a/app/src/main/java/foundation/e/apps/utils/parentFragment/TimeoutFragment.kt +++ b/app/src/main/java/foundation/e/apps/utils/parentFragment/TimeoutFragment.kt @@ -18,13 +18,21 @@ package foundation.e.apps.utils.parentFragment import android.app.Activity +import android.os.Bundle import android.view.KeyEvent +import android.view.View import androidx.annotation.LayoutRes import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.MainActivityViewModel import foundation.e.apps.R +import foundation.e.apps.utils.eventBus.AppEvent +import foundation.e.apps.utils.eventBus.EventBus +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filter +import timber.log.Timber /* * Parent class (extending fragment) for fragments which can display a timeout dialog @@ -40,6 +48,16 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { */ private var timeoutAlertDialog: AlertDialog? = null + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + lifecycleScope.launchWhenResumed { + EventBus.events.filter { appEvent -> appEvent is AppEvent.AppStatusUpdated } + .collectLatest { + Timber.d("@@@ app status updated") + } + } + } + abstract fun onTimeout() /* @@ -182,7 +200,8 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { */ try { timeoutAlertDialog?.dismiss() - } catch (_: Exception) {} + } catch (_: Exception) { + } timeoutAlertDialog = timeoutAlertDialogBuilder.create() timeoutAlertDialog?.show() @@ -210,7 +229,8 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { if (isTimeoutDialogDisplayed()) { try { timeoutAlertDialog?.dismiss() - } catch (_: Exception) {} + } catch (_: Exception) { + } } } } -- GitLab From 2a450d9b1f8458cdbf85195277e7ad850faa7340 Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Sun, 17 Jul 2022 22:14:22 +0600 Subject: [PATCH 2/5] fixed: install button status after uninstall an app fixed: install button status for app list and search page --- .../ApplicationListFragment.kt | 52 ++++++++++++++----- .../foundation/e/apps/home/HomeFragment.kt | 18 ++++++- .../foundation/e/apps/home/HomeViewModel.kt | 33 +++++++++++- .../e/apps/manager/pkg/PkgManagerBR.kt | 13 ++--- .../e/apps/search/SearchFragment.kt | 36 +++++++++++++ .../e/apps/utils/eventBus/AppEvent.kt | 1 - .../utils/parentFragment/TimeoutFragment.kt | 18 ------- 7 files changed, 126 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt index cb02d6939..aecf3265c 100644 --- a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt +++ b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt @@ -41,6 +41,7 @@ import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.applicationlist.model.ApplicationListRVAdapter import foundation.e.apps.databinding.FragmentApplicationListBinding +import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.Status @@ -51,7 +52,8 @@ import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint -class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_list), FusedAPIInterface { +class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_list), + FusedAPIInterface { private val args: ApplicationListFragmentArgs by navArgs() @@ -115,7 +117,6 @@ class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_li override fun onResume() { super.onResume() - binding.shimmerLayout.startShimmer() val recyclerView = binding.recyclerView recyclerView.recycledViewPool.setMaxRecycledViews(0, 0) @@ -156,10 +157,18 @@ class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_li } viewModel.appListLiveData.observe(viewLifecycleOwner) { + stopLoadingUI() if (!it.isSuccess()) { onTimeout() } else { - isDetailsLoaded = true + val currentList = listAdapter?.currentList + if (it.data != null && !currentList.isNullOrEmpty() && !compareOldFusedAppsListWithNewFusedAppsList( + it.data!!, + currentList + ) + ) { + return@observe + } listAdapter?.setData(it.data!!) listAdapter?.let { adapter -> observeDownloadList(adapter) @@ -169,7 +178,6 @@ class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_li updateProgressOfDownloadingItems(binding.recyclerView, it) } } - stopLoadingUI() } /* @@ -220,15 +228,13 @@ class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_li * * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/478 */ - if (!isDetailsLoaded) { - showLoadingUI() - viewModel.getList( - args.category, - args.browseUrl, - authData, - args.source - ) - } + showLoadingUI() + viewModel.getList( + args.category, + args.browseUrl, + authData, + args.source + ) if (args.source != "Open Source" && args.source != "PWA") { /* @@ -260,6 +266,23 @@ class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_li } } + /** + * @return returns true if there is changes in data, otherwise false + */ + fun compareOldFusedAppsListWithNewFusedAppsList( + newFusedApps: List, + oldFusedApps: List + ): Boolean { + val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() + newFusedApps.forEach { + val indexOfNewFusedApp = newFusedApps.indexOf(it) + if (!fusedAppDiffUtil.areContentsTheSame(it, oldFusedApps[indexOfNewFusedApp])) { + return true + } + } + return false + } + private fun showLoadingUI() { binding.shimmerLayout.startShimmer() binding.shimmerLayout.visibility = View.VISIBLE @@ -280,7 +303,8 @@ class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_li lifecycleScope.launch { adapter.currentList.forEach { fusedApp -> if (fusedApp.status == Status.DOWNLOADING) { - val progress = appProgressViewModel.calculateProgress(fusedApp, downloadProgress) + val progress = + appProgressViewModel.calculateProgress(fusedApp, downloadProgress) if (progress == -1) { return@forEach } diff --git a/app/src/main/java/foundation/e/apps/home/HomeFragment.kt b/app/src/main/java/foundation/e/apps/home/HomeFragment.kt index 83f7c1cee..d1d4f34a2 100644 --- a/app/src/main/java/foundation/e/apps/home/HomeFragment.kt +++ b/app/src/main/java/foundation/e/apps/home/HomeFragment.kt @@ -36,8 +36,10 @@ import foundation.e.apps.R import foundation.e.apps.api.fused.FusedAPIImpl import foundation.e.apps.api.fused.FusedAPIInterface import foundation.e.apps.api.fused.data.FusedApp +import foundation.e.apps.api.fused.data.FusedHome import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.databinding.FragmentHomeBinding +import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.home.model.HomeChildRVAdapter import foundation.e.apps.home.model.HomeParentRVAdapter import foundation.e.apps.manager.download.data.DownloadProgress @@ -49,6 +51,7 @@ import foundation.e.apps.utils.modules.CommonUtilsModule.safeNavigate import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.parentFragment.TimeoutFragment import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint @@ -140,7 +143,11 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(fusedApp)) { ApplicationDialogFragment( title = getString(R.string.dialog_title_paid_app, fusedApp.name), - message = getString(R.string.dialog_paidapp_message, fusedApp.name, fusedApp.price), + message = getString( + R.string.dialog_paidapp_message, + fusedApp.name, + fusedApp.price + ), positiveButtonText = getString(R.string.dialog_confirm), positiveButtonAction = { getApplication(fusedApp) @@ -158,7 +165,11 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface homeViewModel.homeScreenData.observe(viewLifecycleOwner) { stopLoadingUI() if (it.second == ResultStatus.OK) { - if (!homeParentRVAdapter?.currentList.isNullOrEmpty()) { + if (homeParentRVAdapter?.currentList?.isNotEmpty() == true && !homeViewModel.compareNewHomeDataWithOldHomeData( + it.first, + homeParentRVAdapter?.currentList as List + ) + ) { return@observe } dismissTimeoutDialog() @@ -266,6 +277,9 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface appProgressViewModel.downloadProgress.observe(viewLifecycleOwner) { updateProgressOfDownloadingAppItemViews(homeParentRVAdapter, it) } + mainActivityViewModel.authData.value?.let { + refreshData(it) + } } override fun onPause() { diff --git a/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt b/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt index 5e20a1033..3244fda7b 100644 --- a/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt +++ b/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt @@ -25,6 +25,7 @@ import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.api.fused.FusedAPIRepository import foundation.e.apps.api.fused.data.FusedHome +import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.utils.enums.ResultStatus import kotlinx.coroutines.launch import javax.inject.Inject @@ -44,7 +45,8 @@ class HomeViewModel @Inject constructor( fun getHomeScreenData(authData: AuthData) { viewModelScope.launch { - homeScreenData.postValue(fusedAPIRepository.getHomeScreenData(authData)) + val screenData = fusedAPIRepository.getHomeScreenData(authData) + homeScreenData.postValue(screenData) } } @@ -57,4 +59,33 @@ class HomeViewModel @Inject constructor( fusedAPIRepository.isFusedHomesEmpty(it) } ?: true } + + fun compareNewHomeDataWithOldHomeData( + newHomeData: List, + oldHomeData: List + ): Boolean { + oldHomeData.forEach { + val fusedHome = newHomeData[oldHomeData.indexOf(it)] + if (!it.title.contentEquals(fusedHome.title) || !areOldAndNewFusedAppListSame(it, fusedHome)) { + return true + } + } + return false + } + + private fun areOldAndNewFusedAppListSame( + it: FusedHome, + fusedHome: FusedHome, + ): Boolean { + val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() + + it.list.forEach { oldFusedApp -> + val indexOfOldFusedApp = it.list.indexOf(oldFusedApp) + val fusedApp = fusedHome.list[indexOfOldFusedApp] + if (!fusedAppDiffUtil.areContentsTheSame(oldFusedApp, fusedApp)) { + return false + } + } + return true + } } diff --git a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt index 7f915a8bc..665150bdd 100644 --- a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt +++ b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt @@ -26,9 +26,10 @@ import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.api.faultyApps.FaultyAppRepository import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.utils.enums.Status -import foundation.e.apps.utils.eventBus.AppEvent -import foundation.e.apps.utils.eventBus.EventBus -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject import javax.inject.Named @@ -67,16 +68,10 @@ open class PkgManagerBR : BroadcastReceiver() { Intent.ACTION_PACKAGE_ADDED -> { updateDownloadStatus(pkgName) removeFaultyAppByPackageName(pkgName) - MainScope().launch { - EventBus.invokeEvent(AppEvent.AppStatusUpdated()) - } } Intent.ACTION_PACKAGE_REMOVED -> { if (!isUpdating) deleteDownload(pkgName) removeFaultyAppByPackageName(pkgName) - MainScope().launch { - EventBus.invokeEvent(AppEvent.AppStatusUpdated()) - } } PkgManagerModule.ERROR_PACKAGE_INSTALL -> { Timber.e("Installation failed due to error: $extra") diff --git a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt index 4bf1e3bd2..c4ba54973 100644 --- a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt +++ b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt @@ -50,6 +50,7 @@ import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.applicationlist.model.ApplicationListRVAdapter import foundation.e.apps.databinding.FragmentSearchBinding +import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.Status @@ -165,6 +166,14 @@ class SearchFragment : if (it.data?.first.isNullOrEmpty()) { noAppsFoundLayout?.visibility = View.VISIBLE } else { + val currentList = listAdapter?.currentList + if (it.data?.first != null && !currentList.isNullOrEmpty() && !compareOldFusedAppsListWithNewFusedAppsList( + it.data?.first!!, + currentList + ) + ) { + return@observe + } listAdapter?.setData(it.data!!.first) binding.loadingProgressBar.isVisible = it.data!!.second stopLoadingUI() @@ -203,6 +212,27 @@ class SearchFragment : } } + /** + * @return returns true if there is changes in data, otherwise false + */ + fun compareOldFusedAppsListWithNewFusedAppsList( + newFusedApps: List, + oldFusedApps: List + ): Boolean { + val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() + if (newFusedApps.size != oldFusedApps.size) { + return true + } + + newFusedApps.forEach { + val indexOfNewFusedApp = newFusedApps.indexOf(it) + if (!fusedAppDiffUtil.areContentsTheSame(it, oldFusedApps[indexOfNewFusedApp])) { + return true + } + } + return false + } + private fun observeDownloadList(applicationListRVAdapter: ApplicationListRVAdapter) { mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list -> val searchList = @@ -281,6 +311,12 @@ class SearchFragment : appProgressViewModel.downloadProgress.observe(viewLifecycleOwner) { updateProgressOfInstallingApps(it) } + + if(searchText.isNotEmpty()) { + mainActivityViewModel.authData.value?.let { + refreshData(it) + } + } } override fun onPause() { diff --git a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt index 29844642a..a3a7306ef 100644 --- a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt +++ b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt @@ -22,5 +22,4 @@ package foundation.e.apps.utils.eventBus sealed class AppEvent(val data: Any) { class SignatureMissMatchError(packageName: String) : AppEvent(packageName) - class AppStatusUpdated: AppEvent("") } diff --git a/app/src/main/java/foundation/e/apps/utils/parentFragment/TimeoutFragment.kt b/app/src/main/java/foundation/e/apps/utils/parentFragment/TimeoutFragment.kt index 3926b7f2a..e5e4c6453 100644 --- a/app/src/main/java/foundation/e/apps/utils/parentFragment/TimeoutFragment.kt +++ b/app/src/main/java/foundation/e/apps/utils/parentFragment/TimeoutFragment.kt @@ -18,21 +18,13 @@ package foundation.e.apps.utils.parentFragment import android.app.Activity -import android.os.Bundle import android.view.KeyEvent -import android.view.View import androidx.annotation.LayoutRes import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.MainActivityViewModel import foundation.e.apps.R -import foundation.e.apps.utils.eventBus.AppEvent -import foundation.e.apps.utils.eventBus.EventBus -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.filter -import timber.log.Timber /* * Parent class (extending fragment) for fragments which can display a timeout dialog @@ -48,16 +40,6 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { */ private var timeoutAlertDialog: AlertDialog? = null - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - lifecycleScope.launchWhenResumed { - EventBus.events.filter { appEvent -> appEvent is AppEvent.AppStatusUpdated } - .collectLatest { - Timber.d("@@@ app status updated") - } - } - } - abstract fun onTimeout() /* -- GitLab From abcc66e9a03ec13573c0584472783844beae6950 Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Tue, 19 Jul 2022 15:23:14 +0600 Subject: [PATCH 3/5] fixed: memory leak in app list page --- .../ApplicationListFragment.kt | 23 ++------------- .../ApplicationListViewModel.kt | 24 ++++++++++++++- .../foundation/e/apps/home/HomeFragment.kt | 4 +-- .../foundation/e/apps/home/HomeViewModel.kt | 9 +++++- .../e/apps/search/SearchFragment.kt | 29 +++---------------- .../e/apps/search/SearchViewModel.kt | 22 ++++++++++++++ 6 files changed, 61 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt index aecf3265c..a1a1468a8 100644 --- a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt +++ b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt @@ -41,7 +41,6 @@ import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.applicationlist.model.ApplicationListRVAdapter import foundation.e.apps.databinding.FragmentApplicationListBinding -import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.Status @@ -52,7 +51,8 @@ import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint -class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_list), +class ApplicationListFragment : + TimeoutFragment(R.layout.fragment_application_list), FusedAPIInterface { private val args: ApplicationListFragmentArgs by navArgs() @@ -162,7 +162,7 @@ class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_li onTimeout() } else { val currentList = listAdapter?.currentList - if (it.data != null && !currentList.isNullOrEmpty() && !compareOldFusedAppsListWithNewFusedAppsList( + if (it.data != null && !currentList.isNullOrEmpty() && !viewModel.hasAnyChangeBetweenOldFusedAppsListAndNewFusedAppsList( it.data!!, currentList ) @@ -266,23 +266,6 @@ class ApplicationListFragment : TimeoutFragment(R.layout.fragment_application_li } } - /** - * @return returns true if there is changes in data, otherwise false - */ - fun compareOldFusedAppsListWithNewFusedAppsList( - newFusedApps: List, - oldFusedApps: List - ): Boolean { - val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() - newFusedApps.forEach { - val indexOfNewFusedApp = newFusedApps.indexOf(it) - if (!fusedAppDiffUtil.areContentsTheSame(it, oldFusedApps[indexOfNewFusedApp])) { - return true - } - } - return false - } - private fun showLoadingUI() { binding.shimmerLayout.startShimmer() binding.shimmerLayout.visibility = View.VISIBLE diff --git a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt index 0d689ef75..c955ba59a 100644 --- a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt +++ b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt @@ -28,6 +28,7 @@ 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.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.utils.enums.Origin import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -76,7 +77,7 @@ class ApplicationListViewModel @Inject constructor( private var hasNextStreamCluster = false fun getList(category: String, browseUrl: String, authData: AuthData, source: String) { - if (appListLiveData.value?.data?.isNotEmpty() == true || isLoading) { + if (isLoading) { return } viewModelScope.launch(Dispatchers.IO) { @@ -97,6 +98,27 @@ class ApplicationListViewModel @Inject constructor( } } + /** + * @return returns true if there is changes in data, otherwise false + */ + fun hasAnyChangeBetweenOldFusedAppsListAndNewFusedAppsList( + newFusedApps: List, + oldFusedApps: List + ): Boolean { + val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() + if (newFusedApps.size != oldFusedApps.size) { + return true + } + + newFusedApps.forEach { + val indexOfNewFusedApp = newFusedApps.indexOf(it) + if (!fusedAppDiffUtil.areContentsTheSame(it, oldFusedApps[indexOfNewFusedApp])) { + return true + } + } + return false + } + /** * 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 diff --git a/app/src/main/java/foundation/e/apps/home/HomeFragment.kt b/app/src/main/java/foundation/e/apps/home/HomeFragment.kt index d1d4f34a2..acb664865 100644 --- a/app/src/main/java/foundation/e/apps/home/HomeFragment.kt +++ b/app/src/main/java/foundation/e/apps/home/HomeFragment.kt @@ -39,7 +39,6 @@ import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.api.fused.data.FusedHome import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.databinding.FragmentHomeBinding -import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.home.model.HomeChildRVAdapter import foundation.e.apps.home.model.HomeParentRVAdapter import foundation.e.apps.manager.download.data.DownloadProgress @@ -51,7 +50,6 @@ import foundation.e.apps.utils.modules.CommonUtilsModule.safeNavigate import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.parentFragment.TimeoutFragment import kotlinx.coroutines.launch -import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint @@ -165,7 +163,7 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface homeViewModel.homeScreenData.observe(viewLifecycleOwner) { stopLoadingUI() if (it.second == ResultStatus.OK) { - if (homeParentRVAdapter?.currentList?.isNotEmpty() == true && !homeViewModel.compareNewHomeDataWithOldHomeData( + if (homeParentRVAdapter?.currentList?.isNotEmpty() == true && !homeViewModel.hasAnyChangeBetweenNewHomeDataAndOldHomeData( it.first, homeParentRVAdapter?.currentList as List ) diff --git a/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt b/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt index 3244fda7b..a84f1cc48 100644 --- a/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt +++ b/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt @@ -60,10 +60,17 @@ class HomeViewModel @Inject constructor( } ?: true } - fun compareNewHomeDataWithOldHomeData( + /** + * @return true, if any change is found, otherwise false + */ + fun hasAnyChangeBetweenNewHomeDataAndOldHomeData( newHomeData: List, oldHomeData: List ): Boolean { + if (newHomeData.size != oldHomeData.size) { + return true + } + oldHomeData.forEach { val fusedHome = newHomeData[oldHomeData.indexOf(it)] if (!it.title.contentEquals(fusedHome.title) || !areOldAndNewFusedAppListSame(it, fusedHome)) { diff --git a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt index c4ba54973..897933317 100644 --- a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt +++ b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt @@ -50,7 +50,6 @@ import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.applicationlist.model.ApplicationListRVAdapter import foundation.e.apps.databinding.FragmentSearchBinding -import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.Status @@ -167,7 +166,7 @@ class SearchFragment : noAppsFoundLayout?.visibility = View.VISIBLE } else { val currentList = listAdapter?.currentList - if (it.data?.first != null && !currentList.isNullOrEmpty() && !compareOldFusedAppsListWithNewFusedAppsList( + if (it.data?.first != null && !currentList.isNullOrEmpty() && !searchViewModel.hasAnyChangeBetweenOldFusedAppsListAndNewFusedAppsList( it.data?.first!!, currentList ) @@ -212,27 +211,6 @@ class SearchFragment : } } - /** - * @return returns true if there is changes in data, otherwise false - */ - fun compareOldFusedAppsListWithNewFusedAppsList( - newFusedApps: List, - oldFusedApps: List - ): Boolean { - val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() - if (newFusedApps.size != oldFusedApps.size) { - return true - } - - newFusedApps.forEach { - val indexOfNewFusedApp = newFusedApps.indexOf(it) - if (!fusedAppDiffUtil.areContentsTheSame(it, oldFusedApps[indexOfNewFusedApp])) { - return true - } - } - return false - } - private fun observeDownloadList(applicationListRVAdapter: ApplicationListRVAdapter) { mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list -> val searchList = @@ -267,7 +245,7 @@ class SearchFragment : override fun refreshData(authData: AuthData) { showLoadingUI() - searchViewModel.getSearchResults(searchText, authData, this) + searchViewModel.getSearchResults(searchText, authData, viewLifecycleOwner) } private fun showLoadingUI() { @@ -312,7 +290,7 @@ class SearchFragment : updateProgressOfInstallingApps(it) } - if(searchText.isNotEmpty()) { + if (searchText.isNotEmpty()) { mainActivityViewModel.authData.value?.let { refreshData(it) } @@ -366,6 +344,7 @@ class SearchFragment : _binding = null searchView = null shimmerLayout = null + recyclerView?.adapter = null recyclerView = null searchHintLayout = null noAppsFoundLayout = null diff --git a/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt b/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt index 3ae636d47..fa8b7f827 100644 --- a/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt +++ b/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt @@ -28,6 +28,7 @@ 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.home.model.HomeChildFusedAppDiffUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import javax.inject.Inject @@ -59,4 +60,25 @@ class SearchViewModel @Inject constructor( } } } + + /** + * @return returns true if there is changes in data, otherwise false + */ + fun hasAnyChangeBetweenOldFusedAppsListAndNewFusedAppsList( + newFusedApps: List, + oldFusedApps: List + ): Boolean { + val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() + if (newFusedApps.size != oldFusedApps.size) { + return true + } + + newFusedApps.forEach { + val indexOfNewFusedApp = newFusedApps.indexOf(it) + if (!fusedAppDiffUtil.areContentsTheSame(it, oldFusedApps[indexOfNewFusedApp])) { + return true + } + } + return false + } } -- GitLab From 96099ae71649669f7970f626e6684d11f826b575 Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Tue, 19 Jul 2022 19:32:40 +0600 Subject: [PATCH 4/5] improved refreshing data for better UX --- .../foundation/e/apps/search/SearchFragment.kt | 7 ++++++- .../e/apps/search/SearchViewModel.kt | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt index 897933317..eca6fe9cb 100644 --- a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt +++ b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt @@ -290,13 +290,18 @@ class SearchFragment : updateProgressOfInstallingApps(it) } - if (searchText.isNotEmpty()) { + if (shouldRefreshData()) { mainActivityViewModel.authData.value?.let { refreshData(it) } } } + private fun shouldRefreshData() = + searchText.isNotEmpty() && recyclerView?.adapter != null && searchViewModel.hasAnyAppInstallStatusChanged( + (recyclerView?.adapter as ApplicationListRVAdapter).currentList + ) + override fun onPause() { binding.shimmerLayout.stopShimmer() super.onPause() diff --git a/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt b/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt index fa8b7f827..3920e77ad 100644 --- a/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt +++ b/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt @@ -29,13 +29,16 @@ 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.home.model.HomeChildFusedAppDiffUtil +import foundation.e.apps.manager.pkg.PkgManagerModule +import foundation.e.apps.utils.enums.Status import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class SearchViewModel @Inject constructor( - private val fusedAPIRepository: FusedAPIRepository + private val fusedAPIRepository: FusedAPIRepository, + private val pkgManagerModule: PkgManagerModule ) : ViewModel() { val searchSuggest: MutableLiveData?> = MutableLiveData() @@ -81,4 +84,17 @@ class SearchViewModel @Inject constructor( } return false } + + fun hasAnyAppInstallStatusChanged(currentList: List): Boolean { + currentList.forEach { + if (it.status == Status.INSTALLATION_ISSUE) { + return@forEach + } + val currentAppStatus = pkgManagerModule.getPackageStatus(it.package_name, it.latest_version_code) + if (it.status != currentAppStatus) { + return true + } + } + return false + } } -- GitLab From e3b84fe5c12a045e5e51a97f1ad4b6407e77938c Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Wed, 20 Jul 2022 18:49:00 +0600 Subject: [PATCH 5/5] refactoring in order to unit test unit test added for fusedapiimpl, fusedapirepository refactoring homepage, applist and search fragment --- app/build.gradle | 16 + .../java/foundation/e/apps/OpenForTesting.kt | 32 ++ .../e/apps/MainActivityViewModel.kt | 15 + .../e/apps/api/cleanapk/CleanAPKRepository.kt | 2 + .../e/apps/api/fused/FusedAPIImpl.kt | 71 +++++ .../e/apps/api/fused/FusedAPIRepository.kt | 18 +- .../ApplicationListFragment.kt | 162 +++++++---- .../ApplicationListViewModel.kt | 34 +-- .../model/ApplicationListRVAdapter.kt | 11 +- .../foundation/e/apps/home/HomeFragment.kt | 154 ++++++---- .../foundation/e/apps/home/HomeViewModel.kt | 41 +-- .../e/apps/home/model/HomeChildRVAdapter.kt | 11 +- .../e/apps/home/model/HomeParentRVAdapter.kt | 3 - .../manager/database/DatabaseRepository.kt | 2 + .../e/apps/manager/pkg/PkgManagerModule.kt | 2 + .../e/apps/search/SearchFragment.kt | 36 +-- .../e/apps/search/SearchViewModel.kt | 40 +-- .../e/apps/updates/UpdatesFragment.kt | 34 +-- .../e/apps/utils/modules/PWAManagerModule.kt | 2 + .../java/foundation/e/apps/OpenForTesting.kt | 21 ++ .../e/apps/FaultyAppRepositoryTest.kt | 83 ++++++ .../foundation/e/apps/FusedApiImplTest.kt | 275 ++++++++++++++++++ .../e/apps/FusedApiRepositoryTest.kt | 54 ++++ build.gradle | 1 + 24 files changed, 860 insertions(+), 260 deletions(-) create mode 100644 app/src/debug/java/foundation/e/apps/OpenForTesting.kt create mode 100644 app/src/release/java/foundation/e/apps/OpenForTesting.kt create mode 100644 app/src/test/java/foundation/e/apps/FaultyAppRepositoryTest.kt create mode 100644 app/src/test/java/foundation/e/apps/FusedApiImplTest.kt create mode 100644 app/src/test/java/foundation/e/apps/FusedApiRepositoryTest.kt diff --git a/app/build.gradle b/app/build.gradle index 694c3c9d6..aba6722a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,6 +5,7 @@ plugins { id 'org.jlleitschuh.gradle.ktlint' version '10.2.0' id 'androidx.navigation.safeargs.kotlin' id 'dagger.hilt.android.plugin' + id 'kotlin-allopen' } def versionMajor = 2 @@ -92,6 +93,11 @@ kapt { correctErrorTypes true } +allOpen { + // allows mocking for classes w/o directly opening them for release builds + annotation 'foundation.e.apps.OpenClass' +} + dependencies { api "com.gitlab.AuroraOSS:gplayapi:0e224071f3" @@ -109,6 +115,15 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + // Optional -- Robolectric environment + testImplementation "androidx.test:core:1.4.0" + // Optional -- Mockito framework + testImplementation "org.mockito:mockito-core:4.6.1" + // Optional -- mockito-kotlin + testImplementation "org.mockito.kotlin:mockito-kotlin:3.2.0" + testImplementation 'org.mockito:mockito-inline:2.13.0' + + // Coil and PhotoView implementation "io.coil-kt:coil:1.4.0" @@ -161,6 +176,7 @@ dependencies { def coroutines_version = "1.6.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" // Room def roomVersion = "2.4.1" diff --git a/app/src/debug/java/foundation/e/apps/OpenForTesting.kt b/app/src/debug/java/foundation/e/apps/OpenForTesting.kt new file mode 100644 index 000000000..d7facea95 --- /dev/null +++ b/app/src/debug/java/foundation/e/apps/OpenForTesting.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 ECORP + * + * 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 + +/** + * This annotation allows us to open some classes for mocking purposes while they are final in + * release builds. + */ +@Target(AnnotationTarget.ANNOTATION_CLASS) +annotation class OpenClass + +/** + * Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds. + */ +@OpenClass +@Target(AnnotationTarget.CLASS) +annotation class OpenForTesting diff --git a/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt index 00e86427e..875997773 100644 --- a/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt @@ -19,6 +19,7 @@ package foundation.e.apps import android.content.Context +import android.content.Intent import android.graphics.Bitmap import android.os.Build import android.os.SystemClock @@ -56,6 +57,7 @@ import foundation.e.apps.utils.enums.isUnFiltered import foundation.e.apps.utils.modules.CommonUtilsModule.NETWORK_CODE_SUCCESS import foundation.e.apps.utils.modules.CommonUtilsModule.timeoutDurationInMillis import foundation.e.apps.utils.modules.DataStoreModule +import foundation.e.apps.utils.modules.PWAManagerModule import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -71,6 +73,7 @@ class MainActivityViewModel @Inject constructor( private val fusedAPIRepository: FusedAPIRepository, private val fusedManagerRepository: FusedManagerRepository, private val pkgManagerModule: PkgManagerModule, + private val pwaManagerModule: PWAManagerModule, private val ecloudRepository: EcloudRepository, private val blockedAppRepository: BlockedAppRepository, private val aC2DMTask: AC2DMTask, @@ -122,6 +125,10 @@ class MainActivityViewModel @Inject constructor( private var isGoogleLoginRunning = false } + fun getUser(): User { + return User.valueOf(userType.value ?: User.UNAVAILABLE.name) + } + private fun setFirstTokenFetchTime() { if (firstAuthDataFetchTime == 0L) { firstAuthDataFetchTime = SystemClock.uptimeMillis() @@ -631,4 +638,12 @@ class MainActivityViewModel @Inject constructor( fun getAppNameByPackageName(packageName: String): String { return pkgManagerModule.getAppNameFromPackageName(packageName) } + + fun getLaunchIntentForPackageName(packageName: String): Intent? { + return pkgManagerModule.getLaunchIntent(packageName) + } + + fun launchPwa(fusedApp: FusedApp) { + pwaManagerModule.launchPwa(fusedApp) + } } diff --git a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanAPKRepository.kt b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanAPKRepository.kt index 9bc56e6fe..45c47a58d 100644 --- a/app/src/main/java/foundation/e/apps/api/cleanapk/CleanAPKRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/cleanapk/CleanAPKRepository.kt @@ -18,6 +18,7 @@ package foundation.e.apps.api.cleanapk +import foundation.e.apps.OpenForTesting import foundation.e.apps.api.cleanapk.data.app.Application import foundation.e.apps.api.cleanapk.data.categories.Categories import foundation.e.apps.api.cleanapk.data.download.Download @@ -26,6 +27,7 @@ import foundation.e.apps.api.cleanapk.data.search.Search import retrofit2.Response import javax.inject.Inject +@OpenForTesting class CleanAPKRepository @Inject constructor( private val cleanAPKInterface: CleanAPKInterface, private val cleanApkAppDetailApi: CleanApkAppDetailApi diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt index 233ae8f1f..226c26de8 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt @@ -47,6 +47,7 @@ import foundation.e.apps.api.fused.data.FusedHome import foundation.e.apps.api.fused.data.Ratings import foundation.e.apps.api.fused.utils.CategoryUtils import foundation.e.apps.api.gplay.GPlayAPIRepository +import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.AppTag @@ -1260,4 +1261,74 @@ class FusedAPIImpl @Inject constructor( } return list } + + /** + * @return true, if any change is found, otherwise false + */ + fun isHomeDataUpdated( + newHomeData: List, + oldHomeData: List + ): Boolean { + if (newHomeData.size != oldHomeData.size) { + return true + } + + oldHomeData.forEach { + val fusedHome = newHomeData[oldHomeData.indexOf(it)] + if (!it.title.contentEquals(fusedHome.title) || !areFusedAppsUpdated(it, fusedHome)) { + return true + } + } + return false + } + + private fun areFusedAppsUpdated( + oldFusedHome: FusedHome, + newFusedHome: FusedHome, + ): Boolean { + val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() + + oldFusedHome.list.forEach { oldFusedApp -> + val indexOfOldFusedApp = oldFusedHome.list.indexOf(oldFusedApp) + val fusedApp = newFusedHome.list[indexOfOldFusedApp] + if (!fusedAppDiffUtil.areContentsTheSame(oldFusedApp, fusedApp)) { + return false + } + } + return true + } + + /** + * @return returns true if there is changes in data, otherwise false + */ + fun isAnyFusedAppUpdated( + newFusedApps: List, + oldFusedApps: List + ): Boolean { + val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() + if (newFusedApps.size != oldFusedApps.size) { + return true + } + + newFusedApps.forEach { + val indexOfNewFusedApp = newFusedApps.indexOf(it) + if (!fusedAppDiffUtil.areContentsTheSame(it, oldFusedApps[indexOfNewFusedApp])) { + return true + } + } + return false + } + + fun isAnyAppInstallStatusChanged(currentList: List): Boolean { + currentList.forEach { + if (it.status == Status.INSTALLATION_ISSUE) { + return@forEach + } + val currentAppStatus = pkgManagerModule.getPackageStatus(it.package_name, it.latest_version_code) + if (it.status != currentAppStatus) { + return true + } + } + return false + } } diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt index e3bdedcd2..6502866f9 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt @@ -124,7 +124,10 @@ class FusedAPIRepository @Inject constructor( return fusedAPIImpl.fetchAuthData(email, aasToken) } - fun getSearchResults(query: String, authData: AuthData): LiveData, Boolean>>> { + fun getSearchResults( + query: String, + authData: AuthData + ): LiveData, Boolean>>> { return fusedAPIImpl.getSearchResults(query, authData) } @@ -167,4 +170,17 @@ class FusedAPIRepository @Inject constructor( fun getFusedAppInstallationStatus(fusedApp: FusedApp): Status { return fusedAPIImpl.getFusedAppInstallationStatus(fusedApp) } + + fun isHomeDataUpdated( + newHomeData: List, + oldHomeData: List + ) = fusedAPIImpl.isHomeDataUpdated(newHomeData, oldHomeData) + + fun isAnyFusedAppUpdated( + newFusedApps: List, + oldFusedApps: List + ) = fusedAPIImpl.isAnyFusedAppUpdated(newFusedApps, oldFusedApps) + + fun isAnyAppInstallStatusChanged(currentList: List) = + fusedAPIImpl.isAnyAppInstallStatusChanged(currentList) } diff --git a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt index a1a1468a8..eed527c4c 100644 --- a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt +++ b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt @@ -36,6 +36,7 @@ import foundation.e.apps.AppProgressViewModel import foundation.e.apps.MainActivityViewModel import foundation.e.apps.PrivacyInfoViewModel import foundation.e.apps.R +import foundation.e.apps.api.ResultSupreme import foundation.e.apps.api.fused.FusedAPIInterface import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.application.subFrags.ApplicationDialogFragment @@ -44,7 +45,6 @@ import foundation.e.apps.databinding.FragmentApplicationListBinding import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.Status -import foundation.e.apps.utils.enums.User import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.parentFragment.TimeoutFragment import kotlinx.coroutines.launch @@ -72,15 +72,7 @@ class ApplicationListFragment : private var _binding: FragmentApplicationListBinding? = null private val binding get() = _binding!! - /* - * Prevent reloading apps. - * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/478 - */ - private var isDetailsLoaded = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } + private lateinit var listAdapter: ApplicationListRVAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -92,6 +84,32 @@ class ApplicationListFragment : view.findNavController().navigate(R.id.categoriesFragment) } } + + setupRecyclerView(view) + observeAppListLiveData() + + /* + * Explanation of double observers in HomeFragment.kt + */ + + mainActivityViewModel.internetConnection.observe(viewLifecycleOwner) { + refreshDataOrRefreshToken(mainActivityViewModel) + } + mainActivityViewModel.authData.observe(viewLifecycleOwner) { + refreshDataOrRefreshToken(mainActivityViewModel) + } + } + + private fun setupRecyclerView(view: View) { + val recyclerView = initRecyclerView() + findNavController().currentDestination?.id?.let { + listAdapter = initAppListAdapter(it) + } + + recyclerView.apply { + adapter = listAdapter + layoutManager = LinearLayoutManager(view?.context) + } } private fun observeDownloadList(adapter: ApplicationListRVAdapter) { @@ -118,80 +136,98 @@ class ApplicationListFragment : override fun onResume() { super.onResume() - val recyclerView = binding.recyclerView - recyclerView.recycledViewPool.setMaxRecycledViews(0, 0) - val listAdapter = - findNavController().currentDestination?.id?.let { - ApplicationListRVAdapter( - this, - privacyInfoViewModel, - appInfoFetchViewModel, - mainActivityViewModel, - it, - pkgManagerModule, - pwaManagerModule, - User.valueOf(mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name), - viewLifecycleOwner - ) { fusedApp -> - if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(fusedApp)) { - ApplicationDialogFragment( - title = getString(R.string.dialog_title_paid_app, fusedApp.name), - message = getString( - R.string.dialog_paidapp_message, - fusedApp.name, - fusedApp.price - ), - positiveButtonText = getString(R.string.dialog_confirm), - positiveButtonAction = { - getApplication(fusedApp) - }, - cancelButtonText = getString(R.string.dialog_cancel), - ).show(childFragmentManager, "HomeFragment") - } - } + if (listAdapter.currentList.isNotEmpty() && viewModel.hasAnyAppInstallStatusChanged(listAdapter.currentList)) { + mainActivityViewModel.authData.value?.let { + refreshData(it) } - - recyclerView.apply { - adapter = listAdapter - layoutManager = LinearLayoutManager(view?.context) } + } + private fun observeAppListLiveData() { viewModel.appListLiveData.observe(viewLifecycleOwner) { stopLoadingUI() if (!it.isSuccess()) { onTimeout() } else { - val currentList = listAdapter?.currentList - if (it.data != null && !currentList.isNullOrEmpty() && !viewModel.hasAnyChangeBetweenOldFusedAppsListAndNewFusedAppsList( - it.data!!, - currentList - ) - ) { + if (!isFusedAppsUpdated(it)) { return@observe } - listAdapter?.setData(it.data!!) - listAdapter?.let { adapter -> - observeDownloadList(adapter) - } - + updateAppListRecyclerView(listAdapter, it) appProgressViewModel.downloadProgress.observe(viewLifecycleOwner) { updateProgressOfDownloadingItems(binding.recyclerView, it) } } } + } - /* - * Explanation of double observers in HomeFragment.kt - */ + private fun isFusedAppsUpdated(it: ResultSupreme>) = + listAdapter.currentList.isEmpty() || it.data != null && viewModel.isAnyAppUpdated( + it.data!!, + listAdapter.currentList + ) - mainActivityViewModel.internetConnection.observe(viewLifecycleOwner) { - refreshDataOrRefreshToken(mainActivityViewModel) + private fun initAppListAdapter( + currentDestinationId: Int + ): ApplicationListRVAdapter { + return ApplicationListRVAdapter( + this, + privacyInfoViewModel, + appInfoFetchViewModel, + mainActivityViewModel, + currentDestinationId, + viewLifecycleOwner + ) { fusedApp -> + if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(fusedApp)) { + showPaidAppMessage(fusedApp) + } } - mainActivityViewModel.authData.observe(viewLifecycleOwner) { - refreshDataOrRefreshToken(mainActivityViewModel) + } + + private fun showPaidAppMessage(fusedApp: FusedApp) { + ApplicationDialogFragment( + title = getString(R.string.dialog_title_paid_app, fusedApp.name), + message = getString( + R.string.dialog_paidapp_message, + fusedApp.name, + fusedApp.price + ), + positiveButtonText = getString(R.string.dialog_confirm), + positiveButtonAction = { + getApplication(fusedApp) + }, + cancelButtonText = getString(R.string.dialog_cancel), + ).show(childFragmentManager, "HomeFragment") + } + + private fun initRecyclerView(): RecyclerView { + val recyclerView = binding.recyclerView + recyclerView.recycledViewPool.setMaxRecycledViews(0, 0) + return recyclerView + } + + private fun updateAppListRecyclerView( + listAdapter: ApplicationListRVAdapter?, + fusedAppResult: ResultSupreme> + ) { + val currentList = listAdapter?.currentList + if (!isFusedAppsUpdated(fusedAppResult, currentList) + ) { + return + } + listAdapter?.setData(fusedAppResult.data!!) + listAdapter?.let { adapter -> + observeDownloadList(adapter) } } + private fun isFusedAppsUpdated( + fusedAppResult: ResultSupreme>, + currentList: MutableList? + ) = currentList.isNullOrEmpty() || fusedAppResult.data != null && viewModel.isFusedAppUpdated( + fusedAppResult.data!!, + currentList + ) + override fun onTimeout() { if (!isTimeoutDialogDisplayed()) { stopLoadingUI() diff --git a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt index c955ba59a..ad4884760 100644 --- a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt +++ b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt @@ -28,7 +28,6 @@ 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.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.utils.enums.Origin import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -101,22 +100,11 @@ class ApplicationListViewModel @Inject constructor( /** * @return returns true if there is changes in data, otherwise false */ - fun hasAnyChangeBetweenOldFusedAppsListAndNewFusedAppsList( + fun isFusedAppUpdated( newFusedApps: List, oldFusedApps: List ): Boolean { - val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() - if (newFusedApps.size != oldFusedApps.size) { - return true - } - - newFusedApps.forEach { - val indexOfNewFusedApp = newFusedApps.indexOf(it) - if (!fusedAppDiffUtil.areContentsTheSame(it, oldFusedApps[indexOfNewFusedApp])) { - return true - } - } - return false + return fusedAPIRepository.isAnyFusedAppUpdated(newFusedApps, oldFusedApps) } /** @@ -209,9 +197,10 @@ class ApplicationListViewModel @Inject constructor( private suspend fun getAdjustedFirstCluster( authData: AuthData, ): ResultSupreme { - return fusedAPIRepository.getAdjustedFirstCluster(authData, streamBundle, clusterPointer).apply { - if (isValidData()) addNewClusterData(this.data!!) - } + return fusedAPIRepository.getAdjustedFirstCluster(authData, streamBundle, clusterPointer) + .apply { + if (isValidData()) addNewClusterData(this.data!!) + } } /** @@ -374,4 +363,15 @@ class ApplicationListViewModel @Inject constructor( private fun getOrigin(source: String) = if (source.contentEquals("Open Source")) Origin.CLEANAPK else Origin.GPLAY + + /** + * @return returns true if there is changes in data, otherwise false + */ + fun isAnyAppUpdated( + newFusedApps: List, + oldFusedApps: List + ) = fusedAPIRepository.isAnyFusedAppUpdated(newFusedApps, oldFusedApps) + + fun hasAnyAppInstallStatusChanged(currentList: List) = + fusedAPIRepository.isAnyAppInstallStatusChanged(currentList) } diff --git a/app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt b/app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt index 1f4fc9fcb..da23c7326 100644 --- a/app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt @@ -47,13 +47,11 @@ import foundation.e.apps.api.fused.FusedAPIInterface import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.applicationlist.ApplicationListFragmentDirections import foundation.e.apps.databinding.ApplicationListItemBinding -import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.search.SearchFragmentDirections import foundation.e.apps.updates.UpdatesFragmentDirections import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.enums.User -import foundation.e.apps.utils.modules.PWAManagerModule import javax.inject.Singleton @Singleton @@ -63,9 +61,6 @@ class ApplicationListRVAdapter( private val appInfoFetchViewModel: AppInfoFetchViewModel, private val mainActivityViewModel: MainActivityViewModel, private val currentDestinationId: Int, - private val pkgManagerModule: PkgManagerModule, - private val pwaManagerModule: PWAManagerModule, - private val user: User, private var lifecycleOwner: LifecycleOwner?, private var paidAppHandler: ((FusedApp) -> Unit)? = null ) : ListAdapter(ApplicationDiffUtil()) { @@ -267,7 +262,7 @@ class ApplicationListRVAdapter( installButton.apply { isEnabled = true setOnClickListener { - val errorMsg = when (user) { + val errorMsg = when (mainActivityViewModel.getUser()) { User.ANONYMOUS, User.UNAVAILABLE -> view.context.getString(R.string.install_blocked_anonymous) User.GOOGLE -> view.context.getString(R.string.install_blocked_google) @@ -458,9 +453,9 @@ class ApplicationListRVAdapter( strokeColor = ContextCompat.getColorStateList(view.context, R.color.colorAccent) setOnClickListener { if (searchApp.is_pwa) { - pwaManagerModule.launchPwa(searchApp) + mainActivityViewModel.launchPwa(searchApp) } else { - context.startActivity(pkgManagerModule.getLaunchIntent(searchApp.package_name)) + context.startActivity(mainActivityViewModel.getLaunchIntentForPackageName(searchApp.package_name)) } } } diff --git a/app/src/main/java/foundation/e/apps/home/HomeFragment.kt b/app/src/main/java/foundation/e/apps/home/HomeFragment.kt index acb664865..7866ead06 100644 --- a/app/src/main/java/foundation/e/apps/home/HomeFragment.kt +++ b/app/src/main/java/foundation/e/apps/home/HomeFragment.kt @@ -86,6 +86,48 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface } } + loadHomePageData() + + homeParentRVAdapter = initHomeParentRVAdapter() + + binding.parentRV.apply { + adapter = homeParentRVAdapter + layoutManager = LinearLayoutManager(view.context) + } + + observeHomeScreenData() + } + + private fun observeHomeScreenData() { + homeViewModel.homeScreenData.observe(viewLifecycleOwner) { + stopLoadingUI() + if (it.second != ResultStatus.OK) { + onTimeout() + return@observe + } + + if (!isHomeDataUpdated(it)) { + return@observe + } + + dismissTimeoutDialog() + homeParentRVAdapter?.setData(it.first) + } + } + + private fun initHomeParentRVAdapter() = HomeParentRVAdapter( + this, + pkgManagerModule, + pwaManagerModule, + User.valueOf(mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name), + mainActivityViewModel, appInfoFetchViewModel, viewLifecycleOwner + ) { fusedApp -> + if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(fusedApp)) { + showPaidAppMessage(fusedApp) + } + } + + private fun loadHomePageData() { /* * Previous code: * internetConnection.observe { @@ -130,54 +172,30 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface mainActivityViewModel.authData.observe(viewLifecycleOwner) { refreshDataOrRefreshToken(mainActivityViewModel) } + } - homeParentRVAdapter = HomeParentRVAdapter( - this, - pkgManagerModule, - pwaManagerModule, - User.valueOf(mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name), - mainActivityViewModel, appInfoFetchViewModel, viewLifecycleOwner - ) { fusedApp -> - if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(fusedApp)) { - ApplicationDialogFragment( - title = getString(R.string.dialog_title_paid_app, fusedApp.name), - message = getString( - R.string.dialog_paidapp_message, - fusedApp.name, - fusedApp.price - ), - positiveButtonText = getString(R.string.dialog_confirm), - positiveButtonAction = { - getApplication(fusedApp) - }, - cancelButtonText = getString(R.string.dialog_cancel), - ).show(childFragmentManager, "HomeFragment") - } - } - - binding.parentRV.apply { - adapter = homeParentRVAdapter - layoutManager = LinearLayoutManager(view.context) - } - - homeViewModel.homeScreenData.observe(viewLifecycleOwner) { - stopLoadingUI() - if (it.second == ResultStatus.OK) { - if (homeParentRVAdapter?.currentList?.isNotEmpty() == true && !homeViewModel.hasAnyChangeBetweenNewHomeDataAndOldHomeData( - it.first, - homeParentRVAdapter?.currentList as List - ) - ) { - return@observe - } - dismissTimeoutDialog() - homeParentRVAdapter?.setData(it.first) - } else { - onTimeout() - } - } + private fun showPaidAppMessage(fusedApp: FusedApp) { + ApplicationDialogFragment( + title = getString(R.string.dialog_title_paid_app, fusedApp.name), + message = getString( + R.string.dialog_paidapp_message, + fusedApp.name, + fusedApp.price + ), + positiveButtonText = getString(R.string.dialog_confirm), + positiveButtonAction = { + getApplication(fusedApp) + }, + cancelButtonText = getString(R.string.dialog_cancel), + ).show(childFragmentManager, "HomeFragment") } + private fun isHomeDataUpdated(homeScreenResult: Pair, ResultStatus>) = + homeParentRVAdapter?.currentList?.isEmpty() == true || homeViewModel.isHomeDataUpdated( + homeScreenResult.first, + homeParentRVAdapter?.currentList as List + ) + override fun onTimeout() { if (homeViewModel.isFusedHomesEmpty() && !isTimeoutDialogDisplayed()) { mainActivityViewModel.uploadFaultyTokenToEcloud("From " + this::class.java.name) @@ -249,21 +267,30 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface childRV: RecyclerView ) { lifecycleScope.launch { - adapter.currentList.forEach { fusedApp -> - if (fusedApp.status == Status.DOWNLOADING) { - val progress = - appProgressViewModel.calculateProgress(fusedApp, downloadProgress) - if (progress == -1) { - return@forEach - } - val childViewHolder = childRV.findViewHolderForAdapterPosition( - adapter.currentList.indexOf(fusedApp) - ) - childViewHolder?.let { - (childViewHolder as HomeChildRVAdapter.ViewHolder).binding.installButton.text = - "$progress%" - } - } + updateDownloadProgressOfAppList(adapter, downloadProgress, childRV) + } + } + + private suspend fun updateDownloadProgressOfAppList( + adapter: HomeChildRVAdapter, + downloadProgress: DownloadProgress, + childRV: RecyclerView + ) { + adapter.currentList.forEach { fusedApp -> + if (fusedApp.status != Status.DOWNLOADING) { + return@forEach + } + val progress = + appProgressViewModel.calculateProgress(fusedApp, downloadProgress) + if (progress == -1) { + return@forEach + } + val childViewHolder = childRV.findViewHolderForAdapterPosition( + adapter.currentList.indexOf(fusedApp) + ) + childViewHolder?.let { + (childViewHolder as HomeChildRVAdapter.ViewHolder).binding.installButton.text = + "$progress%" } } } @@ -275,8 +302,11 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface appProgressViewModel.downloadProgress.observe(viewLifecycleOwner) { updateProgressOfDownloadingAppItemViews(homeParentRVAdapter, it) } - mainActivityViewModel.authData.value?.let { - refreshData(it) + + if (homeViewModel.isAnyAppInstallStatusChanged(homeParentRVAdapter?.currentList)) { + mainActivityViewModel.authData.value?.let { + refreshData(it) + } } } diff --git a/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt b/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt index a84f1cc48..2e19e78b5 100644 --- a/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt +++ b/app/src/main/java/foundation/e/apps/home/HomeViewModel.kt @@ -24,15 +24,15 @@ import androidx.lifecycle.viewModelScope import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.api.fused.FusedAPIRepository +import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.api.fused.data.FusedHome -import foundation.e.apps.home.model.HomeChildFusedAppDiffUtil import foundation.e.apps.utils.enums.ResultStatus import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( - private val fusedAPIRepository: FusedAPIRepository + private val fusedAPIRepository: FusedAPIRepository, ) : ViewModel() { /* @@ -60,39 +60,18 @@ class HomeViewModel @Inject constructor( } ?: true } - /** - * @return true, if any change is found, otherwise false - */ - fun hasAnyChangeBetweenNewHomeDataAndOldHomeData( + fun isHomeDataUpdated( newHomeData: List, oldHomeData: List - ): Boolean { - if (newHomeData.size != oldHomeData.size) { - return true - } + ) = fusedAPIRepository.isHomeDataUpdated(newHomeData, oldHomeData) - oldHomeData.forEach { - val fusedHome = newHomeData[oldHomeData.indexOf(it)] - if (!it.title.contentEquals(fusedHome.title) || !areOldAndNewFusedAppListSame(it, fusedHome)) { - return true - } + fun isAnyAppInstallStatusChanged(currentList: List?): Boolean { + if (currentList == null) { + return false } - return false - } - private fun areOldAndNewFusedAppListSame( - it: FusedHome, - fusedHome: FusedHome, - ): Boolean { - val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() - - it.list.forEach { oldFusedApp -> - val indexOfOldFusedApp = it.list.indexOf(oldFusedApp) - val fusedApp = fusedHome.list[indexOfOldFusedApp] - if (!fusedAppDiffUtil.areContentsTheSame(oldFusedApp, fusedApp)) { - return false - } - } - return true + val appList = mutableListOf() + currentList.forEach { appList.addAll(it.list) } + return fusedAPIRepository.isAnyAppInstallStatusChanged(appList) } } diff --git a/app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt b/app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt index ae17f2793..d9b7aa5d7 100644 --- a/app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt @@ -41,19 +41,14 @@ import foundation.e.apps.api.fused.FusedAPIInterface import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.databinding.HomeChildListItemBinding import foundation.e.apps.home.HomeFragmentDirections -import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.enums.User -import foundation.e.apps.utils.modules.PWAManagerModule class HomeChildRVAdapter( private var fusedAPIInterface: FusedAPIInterface?, - private val pkgManagerModule: PkgManagerModule, - private val pwaManagerModule: PWAManagerModule, private val appInfoFetchViewModel: AppInfoFetchViewModel, private val mainActivityViewModel: MainActivityViewModel, - private val user: User, private var lifecycleOwner: LifecycleOwner?, private var paidAppHandler: ((FusedApp) -> Unit)? = null ) : ListAdapter(HomeChildFusedAppDiffUtil()) { @@ -153,7 +148,7 @@ class HomeChildRVAdapter( private fun HomeChildListItemBinding.handleBlocked(view: View) { installButton.setOnClickListener { - val errorMsg = when (user) { + val errorMsg = when (mainActivityViewModel.getUser()) { User.ANONYMOUS, User.UNAVAILABLE -> view.context.getString(R.string.install_blocked_anonymous) User.GOOGLE -> view.context.getString(R.string.install_blocked_google) @@ -265,9 +260,9 @@ class HomeChildRVAdapter( ContextCompat.getColorStateList(view.context, R.color.colorAccent) setOnClickListener { if (homeApp.is_pwa) { - pwaManagerModule.launchPwa(homeApp) + mainActivityViewModel.launchPwa(homeApp) } else { - context.startActivity(pkgManagerModule.getLaunchIntent(homeApp.package_name)) + context.startActivity(mainActivityViewModel.getLaunchIntentForPackageName(homeApp.package_name)) } } } diff --git a/app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt b/app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt index 4d65c1112..ab44628ed 100644 --- a/app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt @@ -62,11 +62,8 @@ class HomeParentRVAdapter( val homeChildRVAdapter = HomeChildRVAdapter( fusedAPIInterface, - pkgManagerModule, - pwaManagerModule, appInfoFetchViewModel, mainActivityViewModel, - user, lifecycleOwner, paidAppHandler ) diff --git a/app/src/main/java/foundation/e/apps/manager/database/DatabaseRepository.kt b/app/src/main/java/foundation/e/apps/manager/database/DatabaseRepository.kt index 277f0dee7..6344a6c75 100644 --- a/app/src/main/java/foundation/e/apps/manager/database/DatabaseRepository.kt +++ b/app/src/main/java/foundation/e/apps/manager/database/DatabaseRepository.kt @@ -2,6 +2,7 @@ package foundation.e.apps.manager.database import androidx.lifecycle.LiveData import androidx.lifecycle.asFlow +import foundation.e.apps.OpenForTesting import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.database.fusedDownload.FusedDownloadDAO import kotlinx.coroutines.flow.Flow @@ -9,6 +10,7 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton +@OpenForTesting class DatabaseRepository @Inject constructor( private val fusedDownloadDAO: FusedDownloadDAO ) { diff --git a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt index 20ca99948..c501109e0 100644 --- a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt +++ b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt @@ -29,6 +29,7 @@ import android.content.pm.PackageManager import android.os.Build import androidx.core.content.pm.PackageInfoCompat import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.OpenForTesting import foundation.e.apps.api.fused.FusedAPIImpl import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.utils.enums.Origin @@ -41,6 +42,7 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton +@OpenForTesting class PkgManagerModule @Inject constructor( @ApplicationContext private val context: Context ) { diff --git a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt index eca6fe9cb..2544fd0b9 100644 --- a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt +++ b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt @@ -53,7 +53,6 @@ import foundation.e.apps.databinding.FragmentSearchBinding import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.Status -import foundation.e.apps.utils.enums.User import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.parentFragment.TimeoutFragment import kotlinx.coroutines.launch @@ -133,25 +132,10 @@ class SearchFragment : appInfoFetchViewModel, mainActivityViewModel, it, - pkgManagerModule, - pwaManagerModule, - User.valueOf(mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name), viewLifecycleOwner ) { fusedApp -> if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(fusedApp)) { - ApplicationDialogFragment( - title = getString(R.string.dialog_title_paid_app, fusedApp.name), - message = getString( - R.string.dialog_paidapp_message, - fusedApp.name, - fusedApp.price - ), - positiveButtonText = getString(R.string.dialog_confirm), - positiveButtonAction = { - getApplication(fusedApp) - }, - cancelButtonText = getString(R.string.dialog_cancel), - ).show(childFragmentManager, "SearchFragment") + showPaidAppMessage(fusedApp) } } } @@ -166,7 +150,7 @@ class SearchFragment : noAppsFoundLayout?.visibility = View.VISIBLE } else { val currentList = listAdapter?.currentList - if (it.data?.first != null && !currentList.isNullOrEmpty() && !searchViewModel.hasAnyChangeBetweenOldFusedAppsListAndNewFusedAppsList( + if (it.data?.first != null && !currentList.isNullOrEmpty() && !searchViewModel.isAnyAppUpdated( it.data?.first!!, currentList ) @@ -211,6 +195,22 @@ class SearchFragment : } } + private fun showPaidAppMessage(fusedApp: FusedApp) { + ApplicationDialogFragment( + title = getString(R.string.dialog_title_paid_app, fusedApp.name), + message = getString( + R.string.dialog_paidapp_message, + fusedApp.name, + fusedApp.price + ), + positiveButtonText = getString(R.string.dialog_confirm), + positiveButtonAction = { + getApplication(fusedApp) + }, + cancelButtonText = getString(R.string.dialog_cancel), + ).show(childFragmentManager, "SearchFragment") + } + private fun observeDownloadList(applicationListRVAdapter: ApplicationListRVAdapter) { mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list -> val searchList = diff --git a/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt b/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt index 3920e77ad..2c80d5b0c 100644 --- a/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt +++ b/app/src/main/java/foundation/e/apps/search/SearchViewModel.kt @@ -28,9 +28,7 @@ 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.home.model.HomeChildFusedAppDiffUtil -import foundation.e.apps.manager.pkg.PkgManagerModule -import foundation.e.apps.utils.enums.Status +import foundation.e.apps.manager.fused.FusedManagerRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import javax.inject.Inject @@ -38,11 +36,12 @@ import javax.inject.Inject @HiltViewModel class SearchViewModel @Inject constructor( private val fusedAPIRepository: FusedAPIRepository, - private val pkgManagerModule: PkgManagerModule + private val fusedManagerRepository: FusedManagerRepository ) : ViewModel() { val searchSuggest: MutableLiveData?> = MutableLiveData() - val searchResult: MutableLiveData, Boolean>>> = MutableLiveData() + val searchResult: MutableLiveData, Boolean>>> = + MutableLiveData() fun getSearchSuggestions(query: String, authData: AuthData) { viewModelScope.launch(Dispatchers.IO) { @@ -67,34 +66,11 @@ class SearchViewModel @Inject constructor( /** * @return returns true if there is changes in data, otherwise false */ - fun hasAnyChangeBetweenOldFusedAppsListAndNewFusedAppsList( + fun isAnyAppUpdated( newFusedApps: List, oldFusedApps: List - ): Boolean { - val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() - if (newFusedApps.size != oldFusedApps.size) { - return true - } - - newFusedApps.forEach { - val indexOfNewFusedApp = newFusedApps.indexOf(it) - if (!fusedAppDiffUtil.areContentsTheSame(it, oldFusedApps[indexOfNewFusedApp])) { - return true - } - } - return false - } + ) = fusedAPIRepository.isAnyFusedAppUpdated(newFusedApps, oldFusedApps) - fun hasAnyAppInstallStatusChanged(currentList: List): Boolean { - currentList.forEach { - if (it.status == Status.INSTALLATION_ISSUE) { - return@forEach - } - val currentAppStatus = pkgManagerModule.getPackageStatus(it.package_name, it.latest_version_code) - if (it.status != currentAppStatus) { - return true - } - } - return false - } + fun hasAnyAppInstallStatusChanged(currentList: List) = + fusedAPIRepository.isAnyAppInstallStatusChanged(currentList) } diff --git a/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt b/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt index c8654b0a6..99e04d020 100644 --- a/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt +++ b/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt @@ -48,7 +48,6 @@ import foundation.e.apps.manager.workmanager.InstallWorkManager.INSTALL_WORK_NAM import foundation.e.apps.updates.manager.UpdatesWorkManager import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Status -import foundation.e.apps.utils.enums.User import foundation.e.apps.utils.modules.CommonUtilsModule.safeNavigate import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.parentFragment.TimeoutFragment @@ -103,25 +102,10 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte appInfoFetchViewModel, mainActivityViewModel, it, - pkgManagerModule, - pwaManagerModule, - User.valueOf(mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name), viewLifecycleOwner, ) { fusedApp -> if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(fusedApp)) { - ApplicationDialogFragment( - title = getString(R.string.dialog_title_paid_app, fusedApp.name), - message = getString( - R.string.dialog_paidapp_message, - fusedApp.name, - fusedApp.price - ), - positiveButtonText = getString(R.string.dialog_confirm), - positiveButtonAction = { - getApplication(fusedApp) - }, - cancelButtonText = getString(R.string.dialog_cancel), - ).show(childFragmentManager, "UpdatesFragment") + showPurchasedAppMessage(fusedApp) } } } @@ -162,6 +146,22 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte } } + private fun showPurchasedAppMessage(fusedApp: FusedApp) { + ApplicationDialogFragment( + title = getString(R.string.dialog_title_paid_app, fusedApp.name), + message = getString( + R.string.dialog_paidapp_message, + fusedApp.name, + fusedApp.price + ), + positiveButtonText = getString(R.string.dialog_confirm), + positiveButtonAction = { + getApplication(fusedApp) + }, + cancelButtonText = getString(R.string.dialog_cancel), + ).show(childFragmentManager, "UpdatesFragment") + } + override fun onTimeout() { if (!isTimeoutDialogDisplayed()) { stopLoadingUI() diff --git a/app/src/main/java/foundation/e/apps/utils/modules/PWAManagerModule.kt b/app/src/main/java/foundation/e/apps/utils/modules/PWAManagerModule.kt index 1ea385d85..6d3faa62a 100644 --- a/app/src/main/java/foundation/e/apps/utils/modules/PWAManagerModule.kt +++ b/app/src/main/java/foundation/e/apps/utils/modules/PWAManagerModule.kt @@ -12,6 +12,7 @@ import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.OpenForTesting import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.manager.database.DatabaseRepository import foundation.e.apps.manager.database.fusedDownload.FusedDownload @@ -21,6 +22,7 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton +@OpenForTesting class PWAManagerModule @Inject constructor( @ApplicationContext private val context: Context, private val databaseRepository: DatabaseRepository diff --git a/app/src/release/java/foundation/e/apps/OpenForTesting.kt b/app/src/release/java/foundation/e/apps/OpenForTesting.kt new file mode 100644 index 000000000..372dc732d --- /dev/null +++ b/app/src/release/java/foundation/e/apps/OpenForTesting.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 ECORP + * + * 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 + +@Target(AnnotationTarget.CLASS) +annotation class OpenForTesting diff --git a/app/src/test/java/foundation/e/apps/FaultyAppRepositoryTest.kt b/app/src/test/java/foundation/e/apps/FaultyAppRepositoryTest.kt new file mode 100644 index 000000000..9172759a2 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/FaultyAppRepositoryTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 ECORP + * + * 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 + +import foundation.e.apps.api.faultyApps.FaultyApp +import foundation.e.apps.api.faultyApps.FaultyAppDao +import foundation.e.apps.api.faultyApps.FaultyAppRepository +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +@ExperimentalCoroutinesApi +class FaultyAppRepositoryTest { + private lateinit var faultyAppRepository: FaultyAppRepository + private val fakeFaultyAppDao = FakeFaultyAppDao() + + @Before + fun setup() { + faultyAppRepository = FaultyAppRepository(fakeFaultyAppDao) + } + + @Test + fun addFaultyApp_CheckSize() = runTest { + faultyAppRepository.addFaultyApp("foundation.e.apps", "") + assertEquals("testAddFaultyApp", 1, fakeFaultyAppDao.faultyAppList.size) + } + + @Test + fun getAllFaultyApps_ReturnsAppList() = runTest { + fakeFaultyAppDao.faultyAppList.add(FaultyApp("foundation.e.apps", "")) + fakeFaultyAppDao.faultyAppList.add(FaultyApp("foundation.e.edrive", "")) + fakeFaultyAppDao.faultyAppList.add(FaultyApp("foundation.e.privacycentral", "")) + val faultyAppList = faultyAppRepository.getAllFaultyApps() + assertTrue("testGetAllFaultyApps", faultyAppList[0].packageName.contentEquals("foundation.e.apps")) + } + + @Test + fun deleteFaultyApps_CheckSize() = runTest { + fakeFaultyAppDao.faultyAppList.add(FaultyApp("foundation.e.apps", "")) + fakeFaultyAppDao.faultyAppList.add(FaultyApp("foundation.e.edrive", "")) + fakeFaultyAppDao.faultyAppList.add(FaultyApp("foundation.e.privacycentral", "")) + faultyAppRepository.deleteFaultyAppByPackageName("foundation.e.apps") + assertTrue("testDeleteFaultyApps", fakeFaultyAppDao.faultyAppList.size == 2) + } + + class FakeFaultyAppDao : FaultyAppDao { + val faultyAppList: MutableList = mutableListOf() + + override suspend fun addFaultyApp(faultyApp: FaultyApp): Long { + faultyAppList.add(faultyApp) + return -1 + } + + override suspend fun getFaultyApps(): List { + return faultyAppList + } + + override suspend fun deleteFaultyAppByPackageName(packageName: String): Int { + val isSuccess = faultyAppList.removeIf { + it.packageName.contentEquals(packageName) + } + return if (isSuccess) 1 else -1 + } + } +} diff --git a/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt b/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt new file mode 100644 index 000000000..89f050574 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/FusedApiImplTest.kt @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2022 ECORP + * + * 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 + +import android.content.Context +import foundation.e.apps.api.cleanapk.CleanAPKRepository +import foundation.e.apps.api.fused.FusedAPIImpl +import foundation.e.apps.api.fused.data.FusedApp +import foundation.e.apps.api.gplay.GPlayAPIRepository +import foundation.e.apps.manager.pkg.PkgManagerModule +import foundation.e.apps.utils.enums.Status +import foundation.e.apps.utils.modules.PWAManagerModule +import foundation.e.apps.utils.modules.PreferenceManagerModule +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.eq + +class FusedApiImplTest { + private lateinit var fusedAPIImpl: FusedAPIImpl + + @Mock + private lateinit var pwaManagerModule: PWAManagerModule + + @Mock + private lateinit var pkgManagerModule: PkgManagerModule + + @Mock + private lateinit var context: Context + + @Mock + private lateinit var cleanApkRepository: CleanAPKRepository + + @Mock + private lateinit var gPlayAPIRepository: GPlayAPIRepository + + @Mock + private lateinit var preferenceManagerModule: PreferenceManagerModule + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + fusedAPIImpl = FusedAPIImpl( + cleanApkRepository, + gPlayAPIRepository, + pkgManagerModule, + pwaManagerModule, + preferenceManagerModule, + context + ) + } + + @Test + fun `is any app updated when new list is empty`() { + val oldAppList = mutableListOf( + FusedApp( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone" + ), + FusedApp( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo" + ), + FusedApp( + _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( + FusedApp( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone" + ), + FusedApp( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo" + ), + FusedApp( + _id = "113", + status = Status.UNAVAILABLE, + name = "Demo Three", + package_name = "foundation.e.demothree" + ) + ) + val newAppList = mutableListOf( + FusedApp( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone" + ), + FusedApp( + _id = "112", + status = Status.UNAVAILABLE, + name = "Demo Two", + package_name = "foundation.e.demotwo" + ), + FusedApp( + _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( + FusedApp( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone", + latest_version_code = 123 + ), + FusedApp( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo", + latest_version_code = 123 + ), + FusedApp( + _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( + FusedApp( + _id = "111", + status = Status.UNAVAILABLE, + name = "Demo One", + package_name = "foundation.e.demoone", + latest_version_code = 123 + ), + FusedApp( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo", + latest_version_code = 123 + ), + FusedApp( + _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( + FusedApp( + _id = "111", + status = Status.INSTALLATION_ISSUE, + name = "Demo One", + package_name = "foundation.e.demoone", + latest_version_code = 123 + ), + FusedApp( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo", + latest_version_code = 123 + ), + FusedApp( + _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) + } +} diff --git a/app/src/test/java/foundation/e/apps/FusedApiRepositoryTest.kt b/app/src/test/java/foundation/e/apps/FusedApiRepositoryTest.kt new file mode 100644 index 000000000..e3842065c --- /dev/null +++ b/app/src/test/java/foundation/e/apps/FusedApiRepositoryTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 ECORP + * + * 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 + +import foundation.e.apps.api.fused.FusedAPIImpl +import foundation.e.apps.api.fused.FusedAPIRepository +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any + +class FusedApiRepositoryTest { + private lateinit var fusedApiRepository: FusedAPIRepository + @Mock + private lateinit var fusedApiImple: FusedAPIImpl + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + fusedApiRepository = FusedAPIRepository(fusedApiImple) + } + + @Test + fun isAnyAppUpdated_ReturnsTrue() { + Mockito.`when`(fusedApiImple.isAnyFusedAppUpdated(any(), any())).thenReturn(true) + val isAnyAppUpdated = fusedApiRepository.isAnyFusedAppUpdated(listOf(), listOf()) + assertTrue("isAnyAppUpdated", isAnyAppUpdated) + } + + @Test + fun isAnyInstallStatusChanged_ReturnsTrue() { + Mockito.`when`(fusedApiImple.isAnyAppInstallStatusChanged(any())).thenReturn(true) + val isAnyAppUpdated = fusedApiRepository.isAnyAppInstallStatusChanged(listOf()) + assertTrue("isAnyAppUpdated", isAnyAppUpdated) + } +} diff --git a/build.gradle b/build.gradle index 74adc17df..474556714 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ buildscript { classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10' classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5" classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5" + classpath "org.jetbrains.kotlin:kotlin-allopen:1.6.10" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files -- GitLab