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

Verified Commit 20b30d80 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

fix: update status labels correctly while app gets downloaded/installed

parent ea9be74d
Loading
Loading
Loading
Loading
+27 −20
Original line number Diff line number Diff line
@@ -113,30 +113,37 @@ class AppManagerWrapper @Inject constructor(
            val appDownload = getDownloadList()
                .singleOrNull { it.id.contentEquals(app._id) && it.packageName.contentEquals(app.package_name) }
                ?: return 0

            if (!appDownload.id.contentEquals(app._id) || !appDownload.packageName.contentEquals(app.package_name)) {
                return@let
            return calculateProgress(appDownload, progress)
        }

            if (!isProgressValidForApp(application, progress)) {
                return -1
        return 0
    }

            val downloadingMap = progress.totalSizeBytes.filter { item ->
                appDownload.downloadIdMap.keys.contains(item.key) && item.value > 0
    suspend fun calculateProgress(
        appInstall: AppInstall,
        progress: DownloadProgress
    ): Int {
        val downloadIds = appInstall.downloadIdMap.keys
        if (downloadIds.isEmpty()) {
            // Download request exists but ids not yet populated; show 0% instead of dropping percent.
            return 0
        }

            if (appDownload.downloadIdMap.size > downloadingMap.size) { // All files for download are not ready yet
        val totalSizeBytes = progress.totalSizeBytes
            .filterKeys { downloadIds.contains(it) }
            .values
            .sum()
        if (totalSizeBytes <= 0) {
            return 0
        }

            val totalSizeBytes = downloadingMap.values.sum()
            val downloadedSoFar = progress.bytesDownloadedSoFar.filter { item ->
                appDownload.downloadIdMap.keys.contains(item.key)
            }.values.sum()
            return ((downloadedSoFar / totalSizeBytes.toDouble()) * 100).toInt()
        }
        return 0
        val downloadedSoFar = progress.bytesDownloadedSoFar
            .filterKeys { downloadIds.contains(it) }
            .values
            .sum()

        return ((downloadedSoFar / totalSizeBytes.toDouble()) * 100)
            .toInt()
            .coerceIn(0, 100)
    }

    private suspend fun isProgressValidForApp(
+3 −2
Original line number Diff line number Diff line
@@ -57,8 +57,8 @@ import androidx.compose.ui.unit.dp
import coil.compose.rememberImagePainter
import foundation.e.apps.R
import foundation.e.apps.data.application.data.Application
import foundation.e.apps.ui.compose.theme.AppTheme
import foundation.e.apps.ui.compose.state.InstallButtonAction
import foundation.e.apps.ui.compose.theme.AppTheme

@Composable
fun SearchResultListItem(
@@ -303,7 +303,8 @@ private fun PrivacyBadge(
        Button(
            onClick = onPrimaryClick,
            enabled = uiState.enabled,
            modifier = Modifier.height(40.dp),
            modifier = Modifier
                .height(40.dp),
            shape = RoundedCornerShape(4.dp),
            colors = ButtonDefaults.buttonColors(
                containerColor = when {
+19 −5
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ class InstallStatusReconciler @Inject constructor(
        // Prefer matching active download
        val activeDownload = snapshot.downloads.find { matches(app, it) }
        if (activeDownload != null) {
            val progressPercent = progressPercent(app, progress)
            val progressPercent = progressPercent(activeDownload, progress)
            app.status = activeDownload.status
            return Result(app, progressPercent)
        }
@@ -46,15 +46,29 @@ class InstallStatusReconciler @Inject constructor(
    }

    private fun matches(app: Application, install: AppInstall): Boolean {
        return install.packageName == app.package_name || install.id == app._id
        val pkg = app.package_name
        val id = app._id
        return install.packageName == pkg ||
            install.id == id ||
            install.id == pkg
    }

    private suspend fun progressPercent(
        app: Application,
        activeDownload: AppInstall,
        progress: DownloadProgress?
    ): Int? {
        if (progress == null) return null
        val percent = appManagerWrapper.calculateProgress(app, progress)
        return percent.takeIf { it in 0..100 }
        val percent = appManagerWrapper.calculateProgress(activeDownload, progress)
        if (percent in 0..100) return percent

        // Fallback: compute from the last downloadId emitted by DownloadProgress
        val id = progress.downloadId.takeIf { it != -1L }
        if (id != null && activeDownload.downloadIdMap.containsKey(id)) {
            val total = progress.totalSizeBytes[id] ?: return null
            if (total <= 0) return null
            val done = progress.bytesDownloadedSoFar[id] ?: 0L
            return ((done / total.toDouble()) * 100).toInt().coerceIn(0, 100)
        }
        return null
    }
}
+4 −7
Original line number Diff line number Diff line
@@ -18,8 +18,8 @@

package foundation.e.apps.ui.search.v2

import android.content.SharedPreferences
import android.content.Context
import android.content.SharedPreferences
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
@@ -33,25 +33,23 @@ import foundation.e.apps.data.Constants.PREFERENCE_SHOW_PWA
import foundation.e.apps.data.application.data.Application
import foundation.e.apps.data.application.utils.toApplication
import foundation.e.apps.data.cleanapk.CleanApkRetrofit
import foundation.e.apps.data.enums.Status
import foundation.e.apps.data.preference.AppLoungePreference
import foundation.e.apps.data.search.CleanApkSearchParams
import foundation.e.apps.data.search.PlayStorePagingRepository
import foundation.e.apps.data.search.SearchPagingRepository
import foundation.e.apps.install.download.data.DownloadProgress
import foundation.e.apps.ui.compose.state.InstallButtonAction
import foundation.e.apps.ui.compose.state.InstallStatusReconciler
import foundation.e.apps.ui.compose.state.InstallStatusStream
import foundation.e.apps.ui.compose.state.StatusSnapshot
import androidx.lifecycle.asFlow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -60,9 +58,8 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
import foundation.e.apps.data.enums.Status

private const val SUGGESTION_DEBOUNCE_MS = 200L
private const val SUGGESTION_DEBOUNCE_MS = 500L

enum class SearchTabType {
    STANDARD_APPS,