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

Commit d3c2031d authored by Sayantan Roychowdhury's avatar Sayantan Roychowdhury
Browse files

release-1.13-rc changes

Translations
Issue 1445: fix token refresh, timeout in search
parent 69cf7f53
Loading
Loading
Loading
Loading
+33 −19
Original line number Diff line number Diff line
@@ -21,8 +21,8 @@ package foundation.e.apps.data.fused
import android.content.Context
import android.text.format.Formatter
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import com.aurora.gplayapi.Constants
import com.aurora.gplayapi.SearchSuggestEntry
import com.aurora.gplayapi.data.models.App
@@ -62,6 +62,7 @@ import foundation.e.apps.data.fused.utils.CategoryType
import foundation.e.apps.data.fused.utils.CategoryUtils
import foundation.e.apps.data.fusedDownload.models.FusedDownload
import foundation.e.apps.data.gplay.GplayStoreRepository
import foundation.e.apps.data.gplay.utils.runFlowWithTimeout
import foundation.e.apps.data.preference.PreferenceManagerModule
import foundation.e.apps.install.pkg.PWAManagerModule
import foundation.e.apps.install.pkg.PkgManagerModule
@@ -79,7 +80,7 @@ import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton

typealias GplaySearchResultFlow = Flow<ResultSupreme<Pair<List<FusedApp>, Boolean>>>
typealias GplaySearchResultLiveData = LiveData<ResultSupreme<Pair<List<FusedApp>, Boolean>>>
typealias FusedHomeDeferred = Deferred<ResultSupreme<List<FusedHome>>>

@Singleton
@@ -254,8 +255,9 @@ class FusedApiImpl @Inject constructor(
         */
        return liveData {
            val packageSpecificResults = ArrayList<FusedApp>()

            fetchPackageSpecificResult(authData, query, packageSpecificResults).let {
                if (it.data?.second == true) { // if there are no data to load
                if (it.data?.second != true) { // if there are no data to load
                    emit(it)
                    return@liveData
                }
@@ -289,7 +291,7 @@ class FusedApiImpl @Inject constructor(
                        query,
                        searchResult,
                        packageSpecificResults
                    ).asLiveData()
                    )
                )
            }
        }
@@ -333,10 +335,17 @@ class FusedApiImpl @Inject constructor(
        query: String,
        searchResult: MutableList<FusedApp>,
        packageSpecificResults: ArrayList<FusedApp>
    ): GplaySearchResultFlow = getGplaySearchResult(query).map {
        if (it.first.isNotEmpty()) {
            searchResult.addAll(it.first)
    ): GplaySearchResultLiveData {
        return runFlowWithTimeout({
            getGplaySearchResult(query)
        }, {
            it.second
        }, {
            Pair(listOf(), false)   // empty data for timeout
        }
        ).map {
            if (it.isSuccess()) {
                searchResult.addAll(it.data!!.first)
                ResultSupreme.Success(
                    Pair(
                        filterWithKeywordSearch(
@@ -344,9 +353,14 @@ class FusedApiImpl @Inject constructor(
                            packageSpecificResults,
                            query
                        ),
                it.second
                        it.data!!.second
                    )
                )
            } else {
                it
            }

        }
    }

    private suspend fun fetchOpenSourceSearchResult(
@@ -409,10 +423,10 @@ class FusedApiImpl @Inject constructor(
         * Also send true in the pair to signal more results being loaded.
         */
        if (status != ResultStatus.OK) {
            return ResultSupreme.create(status, Pair(packageSpecificResults, true))
        }
            return ResultSupreme.create(status, Pair(packageSpecificResults, false))
        }
        return ResultSupreme.create(status, Pair(packageSpecificResults, true))
    }

    /*
             * The list packageSpecificResults may contain apps with duplicate package names.
+18 −2
Original line number Diff line number Diff line
@@ -52,11 +52,10 @@ class GplayStoreRepositoryImpl @Inject constructor(
    private val loginSourceRepository: LoginSourceRepository
) : GplayStoreRepository {

    private val authData by lazy { loginSourceRepository.gplayAuth!! }

    override suspend fun getHomeScreenData(): Any {
        val homeScreenData = mutableMapOf<String, List<App>>()
        val homeElements = createTopChartElements()
        val authData = loginSourceRepository.gplayAuth ?: return homeScreenData

        homeElements.forEach {
            val chart = it.value.keys.iterator().next()
@@ -81,10 +80,13 @@ class GplayStoreRepositoryImpl @Inject constructor(
        query: String,
    ): Flow<Pair<List<App>, Boolean>> {
        return flow {

            /*
             * Variable names and logic made same as that of Aurora store.
             * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171
             */
            var authData = loginSourceRepository.gplayAuth ?: return@flow

            val searchHelper =
                SearchHelper(authData).using(gPlayHttpClient)
            val searchBundle = searchHelper.searchResults(query)
@@ -148,6 +150,8 @@ class GplayStoreRepositoryImpl @Inject constructor(
    }

    override suspend fun getSearchSuggestions(query: String): List<SearchSuggestEntry> {
        val authData = loginSourceRepository.gplayAuth ?: return listOf()

        val searchData = mutableListOf<SearchSuggestEntry>()
        withContext(Dispatchers.IO) {
            val searchHelper = SearchHelper(authData).using(gPlayHttpClient)
@@ -157,6 +161,8 @@ class GplayStoreRepositoryImpl @Inject constructor(
    }

    override suspend fun getAppsByCategory(category: String, pageUrl: String?): StreamCluster {
        val authData = loginSourceRepository.gplayAuth ?: return StreamCluster()

        val subCategoryHelper =
            CategoryAppsHelper(authData).using(gPlayHttpClient)

@@ -173,6 +179,8 @@ class GplayStoreRepositoryImpl @Inject constructor(
            return categoryList
        }

        val authData = loginSourceRepository.gplayAuth ?: return categoryList

        withContext(Dispatchers.IO) {
            val categoryHelper = CategoryHelper(authData).using(gPlayHttpClient)
            categoryList.addAll(categoryHelper.getAllCategoriesList(getCategoryType(type)))
@@ -182,6 +190,8 @@ class GplayStoreRepositoryImpl @Inject constructor(

    override suspend fun getAppDetails(packageNameOrId: String): App? {
        var appDetails: App?
        val authData = loginSourceRepository.gplayAuth ?: return null

        withContext(Dispatchers.IO) {
            val appDetailsHelper = AppDetailsHelper(authData).using(gPlayHttpClient)
            appDetails = appDetailsHelper.getAppByPackageName(packageNameOrId)
@@ -191,6 +201,8 @@ class GplayStoreRepositoryImpl @Inject constructor(

    override suspend fun getAppsDetails(packageNamesOrIds: List<String>): List<App> {
        val appDetailsList = mutableListOf<App>()
        val authData = loginSourceRepository.gplayAuth ?: return appDetailsList

        withContext(Dispatchers.IO) {
            val appDetailsHelper = AppDetailsHelper(authData).using(gPlayHttpClient)
            appDetailsList.addAll(appDetailsHelper.getAppByPackageName(packageNamesOrIds))
@@ -267,6 +279,8 @@ class GplayStoreRepositoryImpl @Inject constructor(
        offerType: Int
    ): List<File> {
        val downloadData = mutableListOf<File>()
        val authData = loginSourceRepository.gplayAuth ?: return downloadData

        withContext(Dispatchers.IO) {
            val version = versionCode?.let { it as Int } ?: -1
            val purchaseHelper = PurchaseHelper(authData).using(gPlayHttpClient)
@@ -282,6 +296,8 @@ class GplayStoreRepositoryImpl @Inject constructor(
        offerType: Int
    ): List<File> {
        val downloadData = mutableListOf<File>()
        val authData = loginSourceRepository.gplayAuth ?: return downloadData

        withContext(Dispatchers.IO) {
            val purchaseHelper = PurchaseHelper(authData).using(gPlayHttpClient)
            downloadData.addAll(
+106 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2023  MURENA SAS
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

package foundation.e.apps.data.gplay.utils

import android.os.CountDownTimer
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import foundation.e.apps.data.Constants
import foundation.e.apps.data.ResultSupreme
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * Observing a LiveData/Flow for timeout is not easy.
 * We use a [CountDownTimer] to keep track of intervals between two emissions of data.
 * Also we had to collect items from Flow and emit it to a LiveData to avoid missing data emissions.
 *
 * @param block Code block producing the Flow
 * @param moreItemsToLoad Code block evaluating if more items will be loaded in the Flow.
 * Check if the item passed is the last item or not. If it is the last item, return false.
 * @param timeoutBlock Mandatory code block to execute for timeout.
 * Pass empty data from this block or any other data.
 * @param exceptionBlock Optional code block to execute for any other error.
 *
 * @return LiveData containing items from the Flow from [block], each item
 * wrapped in [ResultSupreme].
 */
suspend fun <T> runFlowWithTimeout(
    block: suspend () -> Flow<T>,
    moreItemsToLoad: suspend (item: T) -> Boolean,
    timeoutBlock: () -> T,
    exceptionBlock: ((e: Exception) -> T?)? = null,
): LiveData<ResultSupreme<T>> {

    return liveData {
        withContext(Dispatchers.Main) {

            val timer =
                Timer(this) {
                    emit(ResultSupreme.Timeout(timeoutBlock()))
                    cancel()
                }


            try {
                withContext(Dispatchers.IO) {
                    timer.start()
                    block().collect { item ->
                        timer.cancel()
                        emit(ResultSupreme.Success(item))
                        if (!moreItemsToLoad(item)) {
                            cancel()
                        }
                        timer.start()
                    }
                }
            } catch (e: Exception) {
                if (e is CancellationException) {
                    return@withContext
                }
                runCatching {
                    emit(
                        ResultSupreme.Error<T>(e.stackTraceToString()).apply {
                            exceptionBlock?.invoke(e)?.let { setData(it) }
                        }
                    )
                }
            }
        }
    }

}

private class Timer(
    private val scope: CoroutineScope,
    private val onTimeout: suspend () -> Unit,
): CountDownTimer(Constants.timeoutDurationInMillis, 1000) {

    override fun onTick(millisUntilFinished: Long) {}

    override fun onFinish() {
        scope.launch {
            onTimeout()
        }
    }
}
 No newline at end of file
+3 −1
Original line number Diff line number Diff line
@@ -67,6 +67,8 @@ class LoginSourceRepository @Inject constructor(

    suspend fun getValidatedAuthData(): ResultSupreme<AuthData?> {
        val authDataValidator = (sources.find { it is AuthDataValidator } as AuthDataValidator)
        return authDataValidator.validateAuthData()
        val validateAuthData = authDataValidator.validateAuthData()
        this.gplayAuth = validateAuthData.data
        return validateAuthData
    }
}
+13 −3
Original line number Diff line number Diff line
@@ -118,7 +118,10 @@ class SearchFragment :

        authObjects.observe(viewLifecycleOwner) {
            val currentQuery = searchView?.query?.toString() ?: ""
            if (it == null || (currentQuery.isNotEmpty() && lastSearch == currentQuery)) return@observe
            if (it == null || shouldIgnore(it, currentQuery)) {
                return@observe
            }

            loadDataWhenNetworkAvailable(it)
        }

@@ -127,6 +130,12 @@ class SearchFragment :
        }
    }

    private fun shouldIgnore(
        authObjectList: List<AuthObject>?,
        currentQuery: String
    ) = currentQuery.isNotEmpty() && searchViewModel.isAuthObjectListSame(authObjectList) &&
        lastSearch == currentQuery

    private fun observeSearchResult(listAdapter: ApplicationListRVAdapter?) {
        searchViewModel.searchResult.observe(viewLifecycleOwner) {
            if (it.data?.first.isNullOrEmpty() && it.data?.second == false) {
@@ -427,7 +436,8 @@ class SearchFragment :
    }

    private fun showKeyboard() {
        val inputMethodManager = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        val inputMethodManager =
            requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        searchView?.javaClass?.getDeclaredField("mSearchSrcTextView")?.runCatching {
            isAccessible = true
            get(searchView) as EditText
Loading