Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d2fba084 authored by Romain Hunault's avatar Romain Hunault 💻
Browse files

Merge branch '5070_progress_indicator' into 'epic_176-all-refactorAndGplay'

5070 progress indicator

See merge request ecorp/apps/apps!49
parents 395eccdd 2c5953bc
Loading
Loading
Loading
Loading
+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)
    }
}
+12 −4
Original line number Diff line number Diff line
@@ -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,
@@ -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,
@@ -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 {
@@ -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)
+49 −35
Original line number Diff line number Diff line
@@ -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
@@ -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?) {
@@ -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()) {
@@ -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() {
@@ -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()
    }
+9 −4
Original line number Diff line number Diff line
@@ -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)
        }
    }
+87 −12
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(
@@ -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)
                }
            }

@@ -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)
@@ -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))
@@ -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)
@@ -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)
@@ -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)
            }
@@ -318,7 +391,8 @@ class ApplicationListRVAdapter(

    private fun ApplicationListItemBinding.handleInstalled(
        view: View,
        searchApp: FusedApp
        searchApp: FusedApp,
        holder: ViewHolder
    ) {
        installButton.apply {
            isEnabled = true
@@ -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