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

Verified Commit f545d61b authored by Saalim Quadri's avatar Saalim Quadri
Browse files

chore: Change button state to cancel as soon as a download gets queued



- Switch to a disabled state, until the install is registered
- restore the state

Signed-off-by: default avatarSaalim Quadri <danascape@gmail.com>
parent 012e2fb5
Loading
Loading
Loading
Loading
Loading
+54 −24
Original line number Diff line number Diff line
@@ -9,31 +9,32 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.asFlow
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import foundation.e.apps.R
import foundation.e.apps.data.application.data.Application
import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.enums.Status
import foundation.e.apps.data.enums.User
import foundation.e.apps.data.enums.isInitialized
import foundation.e.apps.data.enums.isUnFiltered
import foundation.e.apps.install.download.data.DownloadProgress
import foundation.e.apps.ui.AppInfoFetchViewModel
import foundation.e.apps.ui.AppProgressViewModel
import foundation.e.apps.ui.MainActivityViewModel
import foundation.e.apps.ui.application.subFrags.ApplicationDialogFragment
import foundation.e.apps.ui.compose.screens.SearchScreen
import foundation.e.apps.ui.compose.state.ButtonLabel
import foundation.e.apps.ui.compose.state.InstallButtonAction
import foundation.e.apps.ui.compose.state.InstallButtonState
import foundation.e.apps.ui.compose.state.InstallButtonStyle
import foundation.e.apps.ui.compose.state.PurchaseState
import foundation.e.apps.ui.compose.state.mapAppToInstallState
import foundation.e.apps.ui.compose.theme.AppTheme
import foundation.e.apps.ui.AppProgressViewModel
import foundation.e.apps.ui.AppInfoFetchViewModel
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.asFlow
import kotlinx.coroutines.launch

@AndroidEntryPoint
@@ -63,6 +64,7 @@ class SearchFragmentV2 : Fragment(R.layout.fragment_search_v2) {
                val downloadProgress by appProgressViewModel.downloadProgress.observeAsState()
                val progressPercentMap by searchViewModelV2.progressPercentByKey.collectAsState()
                val statusByKey by searchViewModelV2.statusByKey.collectAsState()
                val pendingInstalls by searchViewModelV2.pendingInstalls.collectAsState()

                LaunchedEffect(downloadProgress) {
                    // Retain Compose-based updates as a secondary path for safety.
@@ -74,6 +76,7 @@ class SearchFragmentV2 : Fragment(R.layout.fragment_search_v2) {
                val installButtonStateProvider: (Application) -> InstallButtonState = { app ->
                    val progressKey = app.package_name.takeIf { it.isNotBlank() } ?: app._id
                    val progressPercent = progressPercentMap[progressKey]
                    val isPending = progressKey in pendingInstalls
                    val overrideStatus = statusByKey[progressKey]
                    val purchaseState = when {
                        app.isFree -> PurchaseState.Unknown
@@ -81,15 +84,27 @@ class SearchFragmentV2 : Fragment(R.layout.fragment_search_v2) {
                        else -> PurchaseState.NotPurchased
                    }
                    val isBlocked = appInfoFetchViewModel.isAppInBlockedList(app)
                    val isUnsupported = app.filterLevel.isInitialized() && !app.filterLevel.isUnFiltered()
                    val isUnsupported =
                        app.filterLevel.isInitialized() && !app.filterLevel.isUnFiltered()
                    val originalStatus = app.status
                    val effectiveApp = when {
                        isBlocked -> {
                            app.status = Status.BLOCKED
                            app
                        }

                        else -> app
                    }
                    // Show disabled state
                    if (isPending && (overrideStatus == null || overrideStatus == Status.UNAVAILABLE)) {
                        InstallButtonState(
                            label = ButtonLabel(),
                            enabled = false,
                            style = InstallButtonStyle.AccentOutline,
                            showProgressBar = true,
                            actionIntent = InstallButtonAction.NoOp,
                        )
                    } else {
                        mapAppToInstallState(
                            app = effectiveApp,
                            user = user,
@@ -101,8 +116,8 @@ class SearchFragmentV2 : Fragment(R.layout.fragment_search_v2) {
                            isSelfUpdate = app.package_name == requireContext().packageName,
                            overrideStatus = overrideStatus,
                        )
                        .also {
                            // Restore original status to avoid mutating shared paging instance.
                    }.also {
                        // Restore state
                        app.status = originalStatus
                    }
                }
@@ -113,7 +128,11 @@ class SearchFragmentV2 : Fragment(R.layout.fragment_search_v2) {
                    onBackClick = { requireActivity().onBackPressedDispatcher.onBackPressed() },
                    onClearQuery = { searchViewModelV2.onClearQuery() },
                    onSubmitSearch = { submitted -> searchViewModelV2.onSubmitSearch(submitted) },
                    onSuggestionSelected = { suggestion -> searchViewModelV2.onSuggestionSelected(suggestion) },
                    onSuggestionSelected = { suggestion ->
                        searchViewModelV2.onSuggestionSelected(
                            suggestion
                        )
                    },
                    onDismissSuggestions = { searchViewModelV2.onDismissSuggestions() },
                    onTabSelected = { tab -> searchViewModelV2.onTabSelected(tab) },
                    fossPaging = searchViewModelV2.fossPagingFlow,
@@ -147,14 +166,21 @@ class SearchFragmentV2 : Fragment(R.layout.fragment_search_v2) {
        when (action) {
            InstallButtonAction.Install,
            InstallButtonAction.UpdateSelfConfirm -> {
                searchViewModelV2.markPendingInstall(app)
                mainActivityViewModel.verifyUiFilter(app) {
                    if (mainActivityViewModel.shouldShowPaidAppsSnackBar(app)) {
                        searchViewModelV2.clearPendingInstall(app)
                        return@verifyUiFilter
                    }
                    mainActivityViewModel.getApplication(app)
                }
            }
            InstallButtonAction.CancelDownload -> mainActivityViewModel.cancelDownload(app)

            InstallButtonAction.CancelDownload -> {
                searchViewModelV2.clearPendingInstall(app)
                mainActivityViewModel.cancelDownload(app)
            }

            InstallButtonAction.OpenAppOrPwa -> {
                if (app.is_pwa) {
                    mainActivityViewModel.launchPwa(app)
@@ -164,15 +190,18 @@ class SearchFragmentV2 : Fragment(R.layout.fragment_search_v2) {
                    }
                }
            }

            InstallButtonAction.ShowPaidDialog -> {
                when (mainActivityViewModel.getUser()) {
                    User.GOOGLE -> showPaidAppMessage(app)
                    else -> showPaidSnackbar()
                }
            }

            InstallButtonAction.ShowBlockedSnackbar -> {
                showBlockedSnackbar(app)
            }

            InstallButtonAction.NoOp -> Unit
        }
    }
@@ -205,6 +234,7 @@ class SearchFragmentV2 : Fragment(R.layout.fragment_search_v2) {
            User.ANONYMOUS,
            User.NO_GOOGLE,
            User.UNAVAILABLE -> getString(R.string.install_blocked_anonymous)

            User.GOOGLE -> getString(R.string.install_blocked_google)
        }
        if (errorMsg.isNotBlank()) {
+20 −0
Original line number Diff line number Diff line
@@ -121,6 +121,9 @@ class SearchViewModelV2 @Inject constructor(
    private val _statusByKey = MutableStateFlow<Map<String, Status>>(emptyMap())
    val statusByKey: StateFlow<Map<String, Status>> = _statusByKey.asStateFlow()

    private val _pendingInstalls = MutableStateFlow<Set<String>>(emptySet())
    val pendingInstalls: StateFlow<Set<String>> = _pendingInstalls.asStateFlow()

    val fossPagingFlow = buildCleanApkPagingFlow(
        tab = SearchTabType.OPEN_SOURCE,
        appSource = CleanApkRetrofit.APP_SOURCE_FOSS,
@@ -437,6 +440,23 @@ class SearchViewModelV2 @Inject constructor(
        _statusByKey.update { current ->
            if (current[key] != app.status) current + (key to app.status) else current
        }
        if (app.status != Status.UNAVAILABLE) {
            _pendingInstalls.update { it - key }
        }
    }

    fun markPendingInstall(app: Application) {
        val key = keyFor(app)
        _pendingInstalls.update { it + key }
    }

    fun clearPendingInstall(app: Application) {
        val key = keyFor(app)
        _pendingInstalls.update { it - key }
    }

    fun isPendingInstall(app: Application): Boolean {
        return keyFor(app) in _pendingInstalls.value
    }

    private fun keyFor(app: Application): String {