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

Commit c6ed8b44 authored by Jonathan Klee's avatar Jonathan Klee
Browse files

Merge branch '0000-u-test-fix-search' into 'main'

Fix search feature

See merge request !544
parents 10f09e57 87b5a538
Loading
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -18,10 +18,10 @@
package foundation.e.apps.data.enums

enum class Source {
    PLAY_STORE,
    SYSTEM_APP,
    OPEN_SOURCE,
    PWA;
    PWA,
    SYSTEM_APP,
    PLAY_STORE;

    override fun toString(): String {
        return when (this) {
+22 −23
Original line number Diff line number Diff line
@@ -128,7 +128,6 @@ class SearchFragment :
        // Setup Search Results
        val listAdapter = setupSearchResult(view)

        preventLoadingLessResults()
        observeSearchResult(listAdapter)

        setupSearchFilters()
@@ -178,29 +177,21 @@ class SearchFragment :
        lastSearch == currentQuery

    private fun observeSearchResult(listAdapter: ApplicationListRVAdapter?) {
        searchViewModel.searchResult.observe(viewLifecycleOwner) {
            if (it.data?.first.isNullOrEmpty() && it.data?.second == false) {
        searchViewModel.searchResult.observe(viewLifecycleOwner) { result ->
            val apps = result.data?.first

            if (apps.isNullOrEmpty()) {
                noAppsFoundLayout?.visibility = View.VISIBLE
            } else if (searchViewModel.shouldIgnoreResults()) {
                return@observe
            } else {
                listAdapter?.let { adapter ->
                    observeDownloadList(adapter)
                }
            }
            updateSearchResult(listAdapter, it.data?.first ?: emptyList())
            updateSearchResult(listAdapter, apps ?: emptyList())
            observeScrollOfSearchResult(listAdapter)
        }
    }

    private fun preventLoadingLessResults() {
        searchViewModel.gplaySearchLoaded.observe(viewLifecycleOwner) {
            if (!it) return@observe

            searchViewModel.loadMoreDataIfNeeded(searchText)
        }
    }

    private fun observeScrollOfSearchResult(listAdapter: ApplicationListRVAdapter?) {
        listAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
@@ -224,22 +215,29 @@ class SearchFragment :
        })
    }

    /**
     * @return true if Search result is updated, otherwise false
     */
    private fun updateSearchResult(
        listAdapter: ApplicationListRVAdapter?,
        apps: List<Application>,
    ): Boolean {
    ) {
        val currentApps = listAdapter?.currentList ?: listOf()
        if (!searchViewModel.isAnyAppUpdated(apps, currentApps)) {
            return false
            return
        }

        val filteredApps = searchViewModel.sortApps(apps)
        if (filteredApps.isEmpty()) {
            return
        }

        showData()
        val filteredApps = apps.filter { it.name.isNotBlank() }.distinctBy { it.package_name }
        listAdapter?.setData(filteredApps)
        return true
        listAdapter?.submitList(filteredApps)

        // Scroll to the top with some delays so that the recycler view has the time
        // to process the new results
        recyclerView?.postDelayed(
            { recyclerView?.scrollToPosition(0) },
            SCROLL_TO_TOP_DELAY_MILLIS
        )
    }

    private fun showData() {
@@ -468,7 +466,7 @@ class SearchFragment :
        searchJob = lifecycleScope.launch(Dispatchers.Main.immediate) {
            delay(SEARCH_DEBOUNCE_DELAY_MILLIS)
            authObjects.value?.find { it is AuthObject.GPlayAuth }?.run {
                searchViewModel.getSearchSuggestions(text, this as AuthObject.GPlayAuth)
                searchViewModel.getSearchSuggestions(text)
            }
        }
    }
@@ -545,5 +543,6 @@ class SearchFragment :

    companion object {
        private const val SEARCH_DEBOUNCE_DELAY_MILLIS = 500L
        private const val SCROLL_TO_TOP_DELAY_MILLIS = 100L
    }
}
+42 −58
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import foundation.e.apps.data.exodus.repositories.PrivacyScoreRepository
import foundation.e.apps.data.login.AuthObject
import foundation.e.apps.ui.parentFragment.LoadingViewModel
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
@@ -57,13 +58,9 @@ class SearchViewModel @Inject constructor(
        MutableLiveData()
    val searchResult: LiveData<SearchResult> = _searchResult

    val gplaySearchLoaded: MutableLiveData<Boolean> = MutableLiveData(false)

    private var lastAuthObjects: List<AuthObject>? = null


    private var isLoading: Boolean = false
    private var hasGPlayBeenFetched = false

    @GuardedBy("mutex")
    private val accumulatedList = mutableListOf<Application>()
@@ -74,8 +71,6 @@ class SearchViewModel @Inject constructor(
    private var flagPWA: Boolean = false

    companion object {
        private const val DATA_LOAD_ERROR = "Data load error"
        private const val MIN_SEARCH_DISPLAY_ITEMS = 10
        private const val PREVENT_HTTP_429_DELAY_IN_MS = 1000L
    }

@@ -93,7 +88,7 @@ class SearchViewModel @Inject constructor(
        }
    }

    fun getSearchSuggestions(query: String, gPlayAuth: AuthObject.GPlayAuth) {
    fun getSearchSuggestions(query: String) {
        viewModelScope.launch(IO) {
            searchSuggest.postValue(
                applicationRepository.getSearchSuggestions(query)
@@ -110,6 +105,12 @@ class SearchViewModel @Inject constructor(

        this.lastAuthObjects = authObjects
        super.onLoadData(authObjects, { successObjects, failedObjects ->
            viewModelScope.launch {
                mutex.withLock {
                    accumulatedList.clear()
                }
            }

            successObjects.find { it is AuthObject.CleanApk }?.run {
                fetchCleanApkData(query)
            }
@@ -137,7 +138,6 @@ class SearchViewModel @Inject constructor(
        viewModelScope.launch(IO) {
            val searchResultSupreme = applicationRepository.getCleanApkSearchResults(query)

            hasGPlayBeenFetched = false
            emitFilteredResults(searchResultSupreme)

            if (!searchResultSupreme.isSuccess()) {
@@ -147,12 +147,12 @@ class SearchViewModel @Inject constructor(
    }

    fun loadMore(query: String, autoTriggered: Boolean = false) {
        viewModelScope.launch(Main) {
            if (isLoading) {
                Timber.d("Search result is loading....")
            return
                return@launch
            }

        viewModelScope.launch(IO) {
            if (autoTriggered) {
                delay(PREVENT_HTTP_429_DELAY_IN_MS)
            }
@@ -160,9 +160,14 @@ class SearchViewModel @Inject constructor(
        }
    }

    fun sortApps(apps: List<Application>): List<Application> {
        return apps.filter { it.name.isNotBlank() }.sortedBy { it.source }.distinctBy { it.package_name }
    }

    private fun fetchGplayData(query: String) {
        viewModelScope.launch(IO) {
            isLoading = true

            val gplaySearchResult =
                applicationRepository.getGplaySearchResults(query)

@@ -172,23 +177,23 @@ class SearchViewModel @Inject constructor(
                }
            }

            val currentAppList = mutex.withLock {
                updateCurrentAppList(gplaySearchResult)
            }
            val currentAppList = updateCurrentAppList(gplaySearchResult)

            val finalResult = ResultSupreme.Success(
                Pair(currentAppList.toList(), false)
            )

            hasGPlayBeenFetched = true
            emitFilteredResults(finalResult)

            isLoading = false
        }
    }

    private fun updateCurrentAppList(searchResult: SearchResult): List<Application> {
        val currentAppList = accumulatedList
    private suspend fun updateCurrentAppList(searchResult: SearchResult): List<Application> {
        val currentAppList = mutex.withLock {
            accumulatedList
        }

        currentAppList.removeIf { item -> item.isPlaceHolder }
        currentAppList.addAll(searchResult.data?.first ?: emptyList())
        return currentAppList.distinctBy { it.package_name }
@@ -228,6 +233,7 @@ class SearchViewModel @Inject constructor(

    private suspend fun getFilteredList(): List<Application> = withContext(IO) {
        if (flagNoTrackers) {
            mutex.withLock {
                val deferredCheck = accumulatedList.map {
                    async {
                        if (it.privacyScore == -1) {
@@ -238,7 +244,9 @@ class SearchViewModel @Inject constructor(
                }
                deferredCheck.awaitAll()
            }
        }

        mutex.withLock {
            accumulatedList.filter {
                if (!flagNoTrackers && !flagOpenSource && !flagPWA) return@filter true
                if (flagNoTrackers && !hasTrackers(it)) return@filter true
@@ -247,6 +255,7 @@ class SearchViewModel @Inject constructor(
                false
            }
        }
    }

    /**
     * Pass [result] as null to re-emit already loaded search results with new filters.
@@ -268,42 +277,17 @@ class SearchViewModel @Inject constructor(
        if (result != null) {
            result.data?.first?.let {
                mutex.withLock {
                    accumulatedList.clear()
                    accumulatedList.addAll(it)
                }
            }
        }

        val filteredList = mutex.withLock {
            getFilteredList()
        }

        val isMoreDataLoading = result?.data?.second ?: _searchResult.value?.data?.second ?: false
        val filteredList = getFilteredList()

        _searchResult.postValue(
            ResultSupreme.Success(
                Pair(filteredList.toList(), isMoreDataLoading)
                Pair(filteredList.toList(), false)
            )
        )
        gplaySearchLoaded.postValue(hasGPlayBeenFetched)
    }

    fun loadMoreDataIfNeeded(searchText: String) {
        val searchList =
            searchResult.value?.data?.first?.toMutableList() ?: emptyList()
        val canLoadMore = searchResult.value?.data?.second ?: false

        if (searchList.size < MIN_SEARCH_DISPLAY_ITEMS && canLoadMore) {
            loadMore(searchText, autoTriggered = true)
        }
    }

    fun shouldIgnoreResults(): Boolean {
        val appsList = _searchResult.value?.data?.first

        if (appsList.isNullOrEmpty()) return true

        val appPackageNames = appsList.map { it.package_name }
        return appPackageNames.all { it.isBlank() }
    }
}
+1 −1

File changed.

Contains only whitespace changes.