Loading app/src/main/java/foundation/e/apps/AppProgressViewModel.kt 0 → 100644 +43 −0 Original line number Diff line number Diff line package foundation.e.apps import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.download.data.DownloadProgressLD import foundation.e.apps.manager.fused.FusedManagerRepository import javax.inject.Inject @HiltViewModel class AppProgressViewModel @Inject constructor( downloadProgressLD: DownloadProgressLD, private val fusedManagerRepository: FusedManagerRepository ) : ViewModel() { val downloadProgress = downloadProgressLD suspend fun calculateProgress( fusedApp: FusedApp?, progress: DownloadProgress ): Pair<Long, Long> { fusedApp?.let { app -> val appDownload = fusedManagerRepository.getDownloadList() .singleOrNull { it.id.contentEquals(app._id) && it.package_name.contentEquals(app.package_name) } ?: return Pair(1, 0) if (!appDownload.id.contentEquals(app._id) || !appDownload.package_name.contentEquals(app.package_name)) { return@let } val downloadingMap = progress.totalSizeBytes.filter { item -> appDownload.downloadIdMap.keys.contains(item.key) } val totalSizeBytes = downloadingMap.values.sum() val downloadedSoFar = progress.bytesDownloadedSoFar.filter { item -> appDownload.downloadIdMap.keys.contains(item.key) }.values.sum() return Pair(totalSizeBytes, downloadedSoFar) } return Pair(1, 0) } } app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt +12 −4 Original line number Diff line number Diff line Loading @@ -261,7 +261,7 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) { val fusedApp = applicationViewModel.fusedApp.value ?: FusedApp() when (status) { Status.INSTALLED -> handleInstalled(installButton, view, fusedApp) Status.INSTALLED -> handleInstalled(installButton, view, fusedApp, downloadPB, appSize) Status.UPDATABLE -> handleUpdatable( installButton, view, Loading @@ -270,7 +270,7 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) { appSize ) Status.UNAVAILABLE -> handleUnavaiable(installButton, fusedApp, downloadPB, appSize) Status.QUEUED -> handleQueued(installButton, fusedApp) Status.QUEUED, Status.AWAITING -> handleQueued(installButton, fusedApp, downloadPB, appSize) Status.DOWNLOADING -> handleDownloading( installButton, fusedApp, Loading Loading @@ -367,8 +367,12 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) { private fun handleQueued( installButton: MaterialButton, fusedApp: FusedApp fusedApp: FusedApp, downloadPB: RelativeLayout, appSize: MaterialTextView ) { downloadPB.visibility = View.GONE appSize.visibility = View.VISIBLE installButton.apply { text = getString(R.string.cancel) setOnClickListener { Loading Loading @@ -420,8 +424,12 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) { private fun handleInstalled( installButton: MaterialButton, view: View, fusedApp: FusedApp fusedApp: FusedApp, downloadPB: RelativeLayout, appSize: MaterialTextView ) { downloadPB.visibility = View.GONE appSize.visibility = View.VISIBLE installButton.apply { isEnabled = true text = getString(R.string.open) Loading app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt +49 −35 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.AppProgressViewModel import foundation.e.apps.MainActivityViewModel import foundation.e.apps.PrivacyInfoViewModel import foundation.e.apps.R Loading @@ -51,24 +52,14 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu private val viewModel: ApplicationListViewModel by viewModels() private val privacyInfoViewModel: PrivacyInfoViewModel by viewModels() private val mainActivityViewModel: MainActivityViewModel by activityViewModels() private val appProgressViewModel: AppProgressViewModel by viewModels() private var _binding: FragmentApplicationListBinding? = null private val binding get() = _binding!! private var isDownloadObserverAdded = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mainActivityViewModel.internetConnection.observe(this) { isInternetConnection -> mainActivityViewModel.authData.value?.let { authData -> if (isInternetConnection) { viewModel.getList( args.category, args.browseUrl, authData, args.source ) } } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { Loading @@ -81,24 +72,9 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu view.findNavController().navigate(R.id.categoriesFragment) } } val recyclerView = binding.recyclerView val listAdapter = findNavController().currentDestination?.id?.let { ApplicationListRVAdapter( this, privacyInfoViewModel, it, pkgManagerModule, User.valueOf(mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name), viewLifecycleOwner ) } recyclerView.apply { adapter = listAdapter layoutManager = LinearLayoutManager(view.context) } private fun observeDownloadList() { mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list -> val categoryList = viewModel.appListLiveData.value?.toMutableList() if (!categoryList.isNullOrEmpty()) { Loading @@ -110,12 +86,6 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu viewModel.appListLiveData.value = categoryList } } viewModel.appListLiveData.observe(viewLifecycleOwner) { listAdapter?.setData(it) binding.shimmerLayout.visibility = View.GONE recyclerView.visibility = View.VISIBLE } } override fun onDestroyView() { Loading @@ -126,9 +96,53 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu override fun onResume() { super.onResume() binding.shimmerLayout.startShimmer() val recyclerView = binding.recyclerView recyclerView.recycledViewPool.setMaxRecycledViews(0, 0) val listAdapter = findNavController().currentDestination?.id?.let { ApplicationListRVAdapter( this, privacyInfoViewModel, it, pkgManagerModule, User.valueOf(mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name), viewLifecycleOwner, appProgressViewModel ) } recyclerView.apply { adapter = listAdapter layoutManager = LinearLayoutManager(view?.context) } viewModel.appListLiveData.observe(viewLifecycleOwner) { listAdapter?.setData(it) if (!isDownloadObserverAdded) { observeDownloadList() isDownloadObserverAdded = true } binding.shimmerLayout.visibility = View.GONE recyclerView.visibility = View.VISIBLE } mainActivityViewModel.internetConnection.observe(viewLifecycleOwner) { isInternetConnection -> mainActivityViewModel.authData.value?.let { authData -> if (isInternetConnection) { viewModel.getList( args.category, args.browseUrl, authData, args.source ) } } } } override fun onPause() { isDownloadObserverAdded = false binding.shimmerLayout.stopShimmer() super.onPause() } Loading app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt +9 −4 Original line number Diff line number Diff line Loading @@ -51,10 +51,15 @@ class ApplicationListViewModel @Inject constructor( source ).map { it.package_name } val applicationDetails = fusedAPIRepository.getApplicationDetails( val applicationDetails = if (!source.contentEquals("PWA")) { fusedAPIRepository.getApplicationDetails( packageNames, authData, getOrigin(source) ) } else { fusedAPIRepository.getAppsListBasedOnCategory(category, browseUrl, authData, source) } appListLiveData.postValue(applicationDetails) } } Loading app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt +87 −12 Original line number Diff line number Diff line Loading @@ -25,7 +25,10 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.viewModelScope import androidx.navigation.findNavController import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView Loading @@ -34,6 +37,7 @@ import com.facebook.shimmer.Shimmer import com.facebook.shimmer.Shimmer.Direction.LEFT_TO_RIGHT import com.facebook.shimmer.ShimmerDrawable import com.google.android.material.snackbar.Snackbar import foundation.e.apps.AppProgressViewModel import foundation.e.apps.PrivacyInfoViewModel import foundation.e.apps.R import foundation.e.apps.api.cleanapk.CleanAPKInterface Loading @@ -47,6 +51,7 @@ 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 kotlinx.coroutines.launch import javax.inject.Singleton @Singleton Loading @@ -56,7 +61,8 @@ class ApplicationListRVAdapter( private val currentDestinationId: Int, private val pkgManagerModule: PkgManagerModule, private val user: User, private val lifecycleOwner: LifecycleOwner private val lifecycleOwner: LifecycleOwner, private val appProgressViewModel: AppProgressViewModel ) : ListAdapter<FusedApp, ApplicationListRVAdapter.ViewHolder>(ApplicationDiffUtil()) { private val TAG = ApplicationListRVAdapter::class.java.simpleName Loading @@ -70,7 +76,54 @@ class ApplicationListRVAdapter( .build() inner class ViewHolder(val binding: ApplicationListItemBinding) : RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root), LifecycleOwner { private val lifecycleRegistry = LifecycleRegistry(this) init { lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED } fun onAppear() { lifecycleRegistry.currentState = Lifecycle.State.RESUMED } fun onDisappear() { lifecycleRegistry.currentState = Lifecycle.State.DESTROYED } override fun getLifecycle(): Lifecycle { return lifecycleRegistry } } override fun onViewAttachedToWindow(holder: ViewHolder) { super.onViewAttachedToWindow(holder) Log.d(TAG, "onViewAttachedToWindow: Appeared: ${holder.absoluteAdapterPosition}") holder.onAppear() } override fun onViewDetachedFromWindow(holder: ViewHolder) { holder.onDisappear() Log.d(TAG, "onViewAttachedToWindow: Disappeared: ${holder.absoluteAdapterPosition}") super.onViewDetachedFromWindow(holder) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { Log.d(TAG, "onDetachedFromRecyclerView: ") for (i in 0..recyclerView.childCount) { val view = recyclerView.getChildAt(i) if (view != null) { val holder = recyclerView.getChildViewHolder(view) holder?.let { appProgressViewModel.downloadProgress.removeObservers(holder as LifecycleOwner) (holder as ViewHolder).onDisappear() } } } super.onDetachedFromRecyclerView(recyclerView) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder( Loading Loading @@ -153,25 +206,25 @@ class ApplicationListRVAdapter( } when (searchApp.status) { Status.INSTALLED -> { handleInstalled(view, searchApp) handleInstalled(view, searchApp, holder) } Status.UPDATABLE -> { handleUpdatable(view, searchApp) } Status.UNAVAILABLE -> { handleUnavailable(view, searchApp) handleUnavailable(view, searchApp, holder) } Status.QUEUED, Status.AWAITING, Status.DOWNLOADING -> { handleDownloading(view, searchApp) handleDownloading(view, searchApp, holder) } Status.INSTALLING, Status.UNINSTALLING -> { handleInstalling(view) handleInstalling(view, holder) } Status.BLOCKED -> { handleBlocked(view) } Status.INSTALLATION_ISSUE -> { handleInstallationIssue(view, searchApp) handleInstallationIssue(view, searchApp, holder) } } Loading @@ -181,8 +234,10 @@ class ApplicationListRVAdapter( private fun ApplicationListItemBinding.handleInstallationIssue( view: View, searchApp: FusedApp searchApp: FusedApp, holder: ViewHolder ) { appProgressViewModel.downloadProgress.removeObservers(holder) installButton.apply { isEnabled = true text = view.context.getString(R.string.retry) Loading Loading @@ -262,7 +317,8 @@ class ApplicationListRVAdapter( appPrivacyScore.visibility = View.VISIBLE } private fun ApplicationListItemBinding.handleInstalling(view: View) { private fun ApplicationListItemBinding.handleInstalling(view: View, holder: ViewHolder) { appProgressViewModel.downloadProgress.removeObservers(holder) installButton.apply { isEnabled = false setTextColor(context.getColor(R.color.light_grey)) Loading @@ -272,7 +328,11 @@ class ApplicationListRVAdapter( } } private fun ApplicationListItemBinding.handleDownloading(view: View, searchApp: FusedApp) { private fun ApplicationListItemBinding.handleDownloading( view: View, searchApp: FusedApp, holder: ViewHolder ) { installButton.apply { isEnabled = true text = context.getString(R.string.cancel) Loading @@ -280,13 +340,25 @@ class ApplicationListRVAdapter( backgroundTintList = ContextCompat.getColorStateList(view.context, android.R.color.transparent) strokeColor = ContextCompat.getColorStateList(view.context, R.color.colorAccent) appProgressViewModel.downloadProgress.observe(holder) { appProgressViewModel.viewModelScope.launch { val progress = appProgressViewModel.calculateProgress(searchApp, it) if (progress.second > 0 && progress.second <= progress.first) { text = "${((progress.second / progress.first.toDouble()) * 100).toInt()}%" } } } setOnClickListener { cancelDownload(searchApp) } } } private fun ApplicationListItemBinding.handleUnavailable(view: View, searchApp: FusedApp) { private fun ApplicationListItemBinding.handleUnavailable( view: View, searchApp: FusedApp, holder: ViewHolder ) { installButton.apply { isEnabled = true text = context.getString(R.string.install) Loading @@ -294,6 +366,7 @@ class ApplicationListRVAdapter( backgroundTintList = ContextCompat.getColorStateList(view.context, android.R.color.transparent) strokeColor = ContextCompat.getColorStateList(view.context, R.color.colorAccent) appProgressViewModel.downloadProgress.removeObservers(holder) setOnClickListener { installApplication(searchApp, appIcon) } Loading @@ -318,7 +391,8 @@ class ApplicationListRVAdapter( private fun ApplicationListItemBinding.handleInstalled( view: View, searchApp: FusedApp searchApp: FusedApp, holder: ViewHolder ) { installButton.apply { isEnabled = true Loading @@ -326,6 +400,7 @@ class ApplicationListRVAdapter( setTextColor(Color.WHITE) backgroundTintList = ContextCompat.getColorStateList(view.context, R.color.colorAccent) strokeColor = ContextCompat.getColorStateList(view.context, R.color.colorAccent) appProgressViewModel.downloadProgress.removeObservers(holder) setOnClickListener { context.startActivity(pkgManagerModule.getLaunchIntent(searchApp.package_name)) } Loading Loading
app/src/main/java/foundation/e/apps/AppProgressViewModel.kt 0 → 100644 +43 −0 Original line number Diff line number Diff line package foundation.e.apps import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.download.data.DownloadProgressLD import foundation.e.apps.manager.fused.FusedManagerRepository import javax.inject.Inject @HiltViewModel class AppProgressViewModel @Inject constructor( downloadProgressLD: DownloadProgressLD, private val fusedManagerRepository: FusedManagerRepository ) : ViewModel() { val downloadProgress = downloadProgressLD suspend fun calculateProgress( fusedApp: FusedApp?, progress: DownloadProgress ): Pair<Long, Long> { fusedApp?.let { app -> val appDownload = fusedManagerRepository.getDownloadList() .singleOrNull { it.id.contentEquals(app._id) && it.package_name.contentEquals(app.package_name) } ?: return Pair(1, 0) if (!appDownload.id.contentEquals(app._id) || !appDownload.package_name.contentEquals(app.package_name)) { return@let } val downloadingMap = progress.totalSizeBytes.filter { item -> appDownload.downloadIdMap.keys.contains(item.key) } val totalSizeBytes = downloadingMap.values.sum() val downloadedSoFar = progress.bytesDownloadedSoFar.filter { item -> appDownload.downloadIdMap.keys.contains(item.key) }.values.sum() return Pair(totalSizeBytes, downloadedSoFar) } return Pair(1, 0) } }
app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt +12 −4 Original line number Diff line number Diff line Loading @@ -261,7 +261,7 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) { val fusedApp = applicationViewModel.fusedApp.value ?: FusedApp() when (status) { Status.INSTALLED -> handleInstalled(installButton, view, fusedApp) Status.INSTALLED -> handleInstalled(installButton, view, fusedApp, downloadPB, appSize) Status.UPDATABLE -> handleUpdatable( installButton, view, Loading @@ -270,7 +270,7 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) { appSize ) Status.UNAVAILABLE -> handleUnavaiable(installButton, fusedApp, downloadPB, appSize) Status.QUEUED -> handleQueued(installButton, fusedApp) Status.QUEUED, Status.AWAITING -> handleQueued(installButton, fusedApp, downloadPB, appSize) Status.DOWNLOADING -> handleDownloading( installButton, fusedApp, Loading Loading @@ -367,8 +367,12 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) { private fun handleQueued( installButton: MaterialButton, fusedApp: FusedApp fusedApp: FusedApp, downloadPB: RelativeLayout, appSize: MaterialTextView ) { downloadPB.visibility = View.GONE appSize.visibility = View.VISIBLE installButton.apply { text = getString(R.string.cancel) setOnClickListener { Loading Loading @@ -420,8 +424,12 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) { private fun handleInstalled( installButton: MaterialButton, view: View, fusedApp: FusedApp fusedApp: FusedApp, downloadPB: RelativeLayout, appSize: MaterialTextView ) { downloadPB.visibility = View.GONE appSize.visibility = View.VISIBLE installButton.apply { isEnabled = true text = getString(R.string.open) Loading
app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt +49 −35 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.AppProgressViewModel import foundation.e.apps.MainActivityViewModel import foundation.e.apps.PrivacyInfoViewModel import foundation.e.apps.R Loading @@ -51,24 +52,14 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu private val viewModel: ApplicationListViewModel by viewModels() private val privacyInfoViewModel: PrivacyInfoViewModel by viewModels() private val mainActivityViewModel: MainActivityViewModel by activityViewModels() private val appProgressViewModel: AppProgressViewModel by viewModels() private var _binding: FragmentApplicationListBinding? = null private val binding get() = _binding!! private var isDownloadObserverAdded = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mainActivityViewModel.internetConnection.observe(this) { isInternetConnection -> mainActivityViewModel.authData.value?.let { authData -> if (isInternetConnection) { viewModel.getList( args.category, args.browseUrl, authData, args.source ) } } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { Loading @@ -81,24 +72,9 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu view.findNavController().navigate(R.id.categoriesFragment) } } val recyclerView = binding.recyclerView val listAdapter = findNavController().currentDestination?.id?.let { ApplicationListRVAdapter( this, privacyInfoViewModel, it, pkgManagerModule, User.valueOf(mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name), viewLifecycleOwner ) } recyclerView.apply { adapter = listAdapter layoutManager = LinearLayoutManager(view.context) } private fun observeDownloadList() { mainActivityViewModel.downloadList.observe(viewLifecycleOwner) { list -> val categoryList = viewModel.appListLiveData.value?.toMutableList() if (!categoryList.isNullOrEmpty()) { Loading @@ -110,12 +86,6 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu viewModel.appListLiveData.value = categoryList } } viewModel.appListLiveData.observe(viewLifecycleOwner) { listAdapter?.setData(it) binding.shimmerLayout.visibility = View.GONE recyclerView.visibility = View.VISIBLE } } override fun onDestroyView() { Loading @@ -126,9 +96,53 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu override fun onResume() { super.onResume() binding.shimmerLayout.startShimmer() val recyclerView = binding.recyclerView recyclerView.recycledViewPool.setMaxRecycledViews(0, 0) val listAdapter = findNavController().currentDestination?.id?.let { ApplicationListRVAdapter( this, privacyInfoViewModel, it, pkgManagerModule, User.valueOf(mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name), viewLifecycleOwner, appProgressViewModel ) } recyclerView.apply { adapter = listAdapter layoutManager = LinearLayoutManager(view?.context) } viewModel.appListLiveData.observe(viewLifecycleOwner) { listAdapter?.setData(it) if (!isDownloadObserverAdded) { observeDownloadList() isDownloadObserverAdded = true } binding.shimmerLayout.visibility = View.GONE recyclerView.visibility = View.VISIBLE } mainActivityViewModel.internetConnection.observe(viewLifecycleOwner) { isInternetConnection -> mainActivityViewModel.authData.value?.let { authData -> if (isInternetConnection) { viewModel.getList( args.category, args.browseUrl, authData, args.source ) } } } } override fun onPause() { isDownloadObserverAdded = false binding.shimmerLayout.stopShimmer() super.onPause() } Loading
app/src/main/java/foundation/e/apps/applicationlist/ApplicationListViewModel.kt +9 −4 Original line number Diff line number Diff line Loading @@ -51,10 +51,15 @@ class ApplicationListViewModel @Inject constructor( source ).map { it.package_name } val applicationDetails = fusedAPIRepository.getApplicationDetails( val applicationDetails = if (!source.contentEquals("PWA")) { fusedAPIRepository.getApplicationDetails( packageNames, authData, getOrigin(source) ) } else { fusedAPIRepository.getAppsListBasedOnCategory(category, browseUrl, authData, source) } appListLiveData.postValue(applicationDetails) } } Loading
app/src/main/java/foundation/e/apps/applicationlist/model/ApplicationListRVAdapter.kt +87 −12 Original line number Diff line number Diff line Loading @@ -25,7 +25,10 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.viewModelScope import androidx.navigation.findNavController import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView Loading @@ -34,6 +37,7 @@ import com.facebook.shimmer.Shimmer import com.facebook.shimmer.Shimmer.Direction.LEFT_TO_RIGHT import com.facebook.shimmer.ShimmerDrawable import com.google.android.material.snackbar.Snackbar import foundation.e.apps.AppProgressViewModel import foundation.e.apps.PrivacyInfoViewModel import foundation.e.apps.R import foundation.e.apps.api.cleanapk.CleanAPKInterface Loading @@ -47,6 +51,7 @@ 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 kotlinx.coroutines.launch import javax.inject.Singleton @Singleton Loading @@ -56,7 +61,8 @@ class ApplicationListRVAdapter( private val currentDestinationId: Int, private val pkgManagerModule: PkgManagerModule, private val user: User, private val lifecycleOwner: LifecycleOwner private val lifecycleOwner: LifecycleOwner, private val appProgressViewModel: AppProgressViewModel ) : ListAdapter<FusedApp, ApplicationListRVAdapter.ViewHolder>(ApplicationDiffUtil()) { private val TAG = ApplicationListRVAdapter::class.java.simpleName Loading @@ -70,7 +76,54 @@ class ApplicationListRVAdapter( .build() inner class ViewHolder(val binding: ApplicationListItemBinding) : RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root), LifecycleOwner { private val lifecycleRegistry = LifecycleRegistry(this) init { lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED } fun onAppear() { lifecycleRegistry.currentState = Lifecycle.State.RESUMED } fun onDisappear() { lifecycleRegistry.currentState = Lifecycle.State.DESTROYED } override fun getLifecycle(): Lifecycle { return lifecycleRegistry } } override fun onViewAttachedToWindow(holder: ViewHolder) { super.onViewAttachedToWindow(holder) Log.d(TAG, "onViewAttachedToWindow: Appeared: ${holder.absoluteAdapterPosition}") holder.onAppear() } override fun onViewDetachedFromWindow(holder: ViewHolder) { holder.onDisappear() Log.d(TAG, "onViewAttachedToWindow: Disappeared: ${holder.absoluteAdapterPosition}") super.onViewDetachedFromWindow(holder) } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { Log.d(TAG, "onDetachedFromRecyclerView: ") for (i in 0..recyclerView.childCount) { val view = recyclerView.getChildAt(i) if (view != null) { val holder = recyclerView.getChildViewHolder(view) holder?.let { appProgressViewModel.downloadProgress.removeObservers(holder as LifecycleOwner) (holder as ViewHolder).onDisappear() } } } super.onDetachedFromRecyclerView(recyclerView) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder( Loading Loading @@ -153,25 +206,25 @@ class ApplicationListRVAdapter( } when (searchApp.status) { Status.INSTALLED -> { handleInstalled(view, searchApp) handleInstalled(view, searchApp, holder) } Status.UPDATABLE -> { handleUpdatable(view, searchApp) } Status.UNAVAILABLE -> { handleUnavailable(view, searchApp) handleUnavailable(view, searchApp, holder) } Status.QUEUED, Status.AWAITING, Status.DOWNLOADING -> { handleDownloading(view, searchApp) handleDownloading(view, searchApp, holder) } Status.INSTALLING, Status.UNINSTALLING -> { handleInstalling(view) handleInstalling(view, holder) } Status.BLOCKED -> { handleBlocked(view) } Status.INSTALLATION_ISSUE -> { handleInstallationIssue(view, searchApp) handleInstallationIssue(view, searchApp, holder) } } Loading @@ -181,8 +234,10 @@ class ApplicationListRVAdapter( private fun ApplicationListItemBinding.handleInstallationIssue( view: View, searchApp: FusedApp searchApp: FusedApp, holder: ViewHolder ) { appProgressViewModel.downloadProgress.removeObservers(holder) installButton.apply { isEnabled = true text = view.context.getString(R.string.retry) Loading Loading @@ -262,7 +317,8 @@ class ApplicationListRVAdapter( appPrivacyScore.visibility = View.VISIBLE } private fun ApplicationListItemBinding.handleInstalling(view: View) { private fun ApplicationListItemBinding.handleInstalling(view: View, holder: ViewHolder) { appProgressViewModel.downloadProgress.removeObservers(holder) installButton.apply { isEnabled = false setTextColor(context.getColor(R.color.light_grey)) Loading @@ -272,7 +328,11 @@ class ApplicationListRVAdapter( } } private fun ApplicationListItemBinding.handleDownloading(view: View, searchApp: FusedApp) { private fun ApplicationListItemBinding.handleDownloading( view: View, searchApp: FusedApp, holder: ViewHolder ) { installButton.apply { isEnabled = true text = context.getString(R.string.cancel) Loading @@ -280,13 +340,25 @@ class ApplicationListRVAdapter( backgroundTintList = ContextCompat.getColorStateList(view.context, android.R.color.transparent) strokeColor = ContextCompat.getColorStateList(view.context, R.color.colorAccent) appProgressViewModel.downloadProgress.observe(holder) { appProgressViewModel.viewModelScope.launch { val progress = appProgressViewModel.calculateProgress(searchApp, it) if (progress.second > 0 && progress.second <= progress.first) { text = "${((progress.second / progress.first.toDouble()) * 100).toInt()}%" } } } setOnClickListener { cancelDownload(searchApp) } } } private fun ApplicationListItemBinding.handleUnavailable(view: View, searchApp: FusedApp) { private fun ApplicationListItemBinding.handleUnavailable( view: View, searchApp: FusedApp, holder: ViewHolder ) { installButton.apply { isEnabled = true text = context.getString(R.string.install) Loading @@ -294,6 +366,7 @@ class ApplicationListRVAdapter( backgroundTintList = ContextCompat.getColorStateList(view.context, android.R.color.transparent) strokeColor = ContextCompat.getColorStateList(view.context, R.color.colorAccent) appProgressViewModel.downloadProgress.removeObservers(holder) setOnClickListener { installApplication(searchApp, appIcon) } Loading @@ -318,7 +391,8 @@ class ApplicationListRVAdapter( private fun ApplicationListItemBinding.handleInstalled( view: View, searchApp: FusedApp searchApp: FusedApp, holder: ViewHolder ) { installButton.apply { isEnabled = true Loading @@ -326,6 +400,7 @@ class ApplicationListRVAdapter( setTextColor(Color.WHITE) backgroundTintList = ContextCompat.getColorStateList(view.context, R.color.colorAccent) strokeColor = ContextCompat.getColorStateList(view.context, R.color.colorAccent) appProgressViewModel.downloadProgress.removeObservers(holder) setOnClickListener { context.startActivity(pkgManagerModule.getLaunchIntent(searchApp.package_name)) } Loading