compose: Remove epoxy and view related logic for searching
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -67,7 +67,7 @@ fun DevProfileScreen(
|
||||
) {
|
||||
val apps = viewModel.apps.collectAsLazyPagingItems()
|
||||
|
||||
LaunchedEffect(key1 = Unit) { viewModel.newSearch("pub:$publisherId") }
|
||||
LaunchedEffect(key1 = Unit) { viewModel.search("pub:$publisherId") }
|
||||
|
||||
ScreenContent(
|
||||
apps = apps,
|
||||
|
||||
@@ -72,7 +72,7 @@ fun SearchScreen(onNavigateUp: () -> Unit, viewModel: SearchViewModel = hiltView
|
||||
suggestions = suggestions,
|
||||
results = results,
|
||||
onNavigateUp = onNavigateUp,
|
||||
onSearch = { query -> viewModel.newSearch(query) },
|
||||
onSearch = { query -> viewModel.search(query) },
|
||||
onFetchSuggestions = { query -> viewModel.fetchSuggestions(query) }
|
||||
)
|
||||
}
|
||||
@@ -159,7 +159,8 @@ private fun ScreenContent(
|
||||
suggestions.forEach { suggestion ->
|
||||
SearchSuggestionComposable(
|
||||
searchSuggestEntry = suggestion,
|
||||
onClick = { query -> onRequestSearch(query) }
|
||||
onClick = { query -> onRequestSearch(query) },
|
||||
onAction = { query -> currentQuery = query.trim() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.views
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.content.ContextCompat
|
||||
import coil3.load
|
||||
import coil3.request.transformations
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.airbnb.epoxy.CallbackProp
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.aurora.gplayapi.SearchSuggestEntry
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.ViewSearchSuggestionBinding
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
baseModelClass = BaseModel::class
|
||||
)
|
||||
class SearchSuggestionView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewSearchSuggestionBinding>(context, attrs, defStyleAttr) {
|
||||
|
||||
@ModelProp
|
||||
fun entry(searchSuggestEntry: SearchSuggestEntry) {
|
||||
if (searchSuggestEntry.hasImageContainer()) {
|
||||
binding.img.load(searchSuggestEntry.imageContainer.imageUrl) {
|
||||
transformations(RoundedCornersTransformation(8F))
|
||||
}
|
||||
} else {
|
||||
binding.img.setImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ic_search_suggestion
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
binding.txtTitle.text = searchSuggestEntry.title
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun click(onClickListener: OnClickListener?) {
|
||||
binding.root.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun action(onClickListener: OnClickListener?) {
|
||||
binding.action.setOnClickListener(onClickListener)
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,10 @@ import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.aurora.extensions.navigate
|
||||
import com.aurora.store.MobileNavigationDirections
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.navigation.Screen
|
||||
import com.aurora.store.databinding.FragmentAppsGamesBinding
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
@@ -113,7 +115,7 @@ class GamesContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
|
||||
}.attach()
|
||||
|
||||
binding.searchFab.setOnClickListener {
|
||||
findNavController().navigate(R.id.searchSuggestionFragment)
|
||||
requireContext().navigate(Screen.Search)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.search
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.extensions.hideKeyboard
|
||||
import com.aurora.extensions.showKeyboard
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.AppStreamStash
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.ViewState
|
||||
import com.aurora.store.data.model.ViewState.Loading.getDataAs
|
||||
import com.aurora.store.databinding.FragmentSearchResultBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.controller.GenericCarouselController
|
||||
import com.aurora.store.view.epoxy.controller.SearchCarouselController
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.search.SearchViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SearchResultsFragment : BaseFragment<FragmentSearchResultBinding>(),
|
||||
GenericCarouselController.Callbacks {
|
||||
|
||||
private val viewModel: SearchViewModel by activityViewModels()
|
||||
private val controller = SearchCarouselController(this)
|
||||
|
||||
private var query: String
|
||||
get() = requireArguments().getString("query").orEmpty()
|
||||
set(value) = requireArguments().putString("query", value)
|
||||
|
||||
private var scrollListener: EndlessRecyclerOnScrollListener =
|
||||
object : EndlessRecyclerOnScrollListener(visibleThreshold = 4) {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
viewModel.observe(query)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Search
|
||||
attachSearch()
|
||||
with(binding) {
|
||||
toolbar.apply {
|
||||
setNavigationOnClickListener {
|
||||
binding.searchBar.hideKeyboard()
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_clear -> {
|
||||
binding.searchBar.text?.clear()
|
||||
binding.searchBar.showKeyboard()
|
||||
}
|
||||
|
||||
R.id.action_download -> findNavController().navigate(R.id.downloadFragment)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
recycler.setController(controller)
|
||||
recycler.addOnScrollListener(scrollListener)
|
||||
}
|
||||
|
||||
with(viewModel) {
|
||||
search(query)
|
||||
|
||||
liveData.observe(viewLifecycleOwner) {
|
||||
when (it) {
|
||||
is ViewState.Loading -> {
|
||||
controller.setData(null)
|
||||
}
|
||||
|
||||
is ViewState.Success<*> -> {
|
||||
val stash = it.getDataAs<AppStreamStash>()
|
||||
controller.setData(stash[query])
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachSearch() {
|
||||
binding.searchBar.text = Editable.Factory.getInstance().newEditable(query)
|
||||
|
||||
binding.searchBar.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
binding.toolbar.menu.findItem(R.id.action_clear)?.isVisible = s.isNotBlank()
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {}
|
||||
})
|
||||
|
||||
binding.searchBar.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH
|
||||
|| actionId == KeyEvent.ACTION_DOWN
|
||||
|| actionId == KeyEvent.KEYCODE_ENTER
|
||||
) {
|
||||
|
||||
query = binding.searchBar.text.toString()
|
||||
|
||||
queryViewModel(query)
|
||||
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun queryViewModel(query: String) {
|
||||
scrollListener.resetPageCount()
|
||||
viewModel.search(query)
|
||||
}
|
||||
|
||||
override fun onHeaderClicked(streamCluster: StreamCluster) {
|
||||
openStreamBrowseFragment(streamCluster)
|
||||
}
|
||||
|
||||
override fun onClusterScrolled(streamCluster: StreamCluster) {
|
||||
viewModel.observeCluster(query, streamCluster)
|
||||
}
|
||||
|
||||
override fun onAppClick(app: App) {
|
||||
openDetailsFragment(app.packageName)
|
||||
}
|
||||
|
||||
override fun onAppLongClick(app: App) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.search
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.extensions.hideKeyboard
|
||||
import com.aurora.extensions.showKeyboard
|
||||
import com.aurora.gplayapi.SearchSuggestEntry
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentSearchSuggestionBinding
|
||||
import com.aurora.store.view.epoxy.views.SearchSuggestionViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.search.SearchSuggestionViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SearchSuggestionFragment : BaseFragment<FragmentSearchSuggestionBinding>() {
|
||||
|
||||
private val viewModel: SearchSuggestionViewModel by viewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.toolbar.apply {
|
||||
setNavigationOnClickListener {
|
||||
binding.searchBar.hideKeyboard()
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_clear -> binding.searchBar.text?.clear()
|
||||
R.id.action_download -> findNavController().navigate(R.id.downloadFragment)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.searchSuggestions.collectLatest {
|
||||
updateController(it)
|
||||
}
|
||||
}
|
||||
|
||||
setupSearch()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.searchBar.showKeyboard()
|
||||
}
|
||||
|
||||
private fun updateController(searchSuggestions: List<SearchSuggestEntry>) {
|
||||
binding.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
searchSuggestions.forEach {
|
||||
add(
|
||||
SearchSuggestionViewModel_()
|
||||
.id(it.title)
|
||||
.entry(it)
|
||||
.action { _ ->
|
||||
updateQuery(it.title)
|
||||
}
|
||||
.click { _ ->
|
||||
binding.searchBar.hideKeyboard()
|
||||
search(it.title)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSearch() {
|
||||
binding.searchBar.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
if (s.isNotEmpty()) {
|
||||
val query = s.toString()
|
||||
if (query.isNotEmpty()) {
|
||||
viewModel.observeStreamBundles(query)
|
||||
}
|
||||
}
|
||||
binding.toolbar.menu.findItem(R.id.action_clear)?.isVisible = s.isNotBlank()
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {}
|
||||
})
|
||||
|
||||
binding.searchBar.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH
|
||||
|| actionId == KeyEvent.ACTION_DOWN
|
||||
|| actionId == KeyEvent.KEYCODE_ENTER
|
||||
) {
|
||||
val query = binding.searchBar.text.toString()
|
||||
if (query.isNotEmpty()) {
|
||||
binding.searchBar.hideKeyboard()
|
||||
search(query)
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateQuery(query: String) {
|
||||
binding.searchBar.text = Editable.Factory.getInstance().newEditable(query)
|
||||
binding.searchBar.setSelection(query.length)
|
||||
}
|
||||
|
||||
private fun search(query: String) {
|
||||
findNavController().navigate(
|
||||
SearchSuggestionFragmentDirections
|
||||
.actionSearchSuggestionFragmentToSearchResultsFragment(query)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,11 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.extensions.navigate
|
||||
import com.aurora.extensions.requiresObbDir
|
||||
import com.aurora.store.MobileNavigationDirections
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.navigation.Screen
|
||||
import com.aurora.store.data.model.MinimalApp
|
||||
import com.aurora.store.data.model.PermissionType
|
||||
import com.aurora.store.data.room.download.Download
|
||||
@@ -109,7 +111,7 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
||||
}
|
||||
|
||||
binding.searchFab.setOnClickListener {
|
||||
findNavController().navigate(R.id.searchSuggestionFragment)
|
||||
requireContext().navigate(Screen.Search)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.viewmodel.search
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.SearchSuggestEntry
|
||||
import com.aurora.gplayapi.helpers.SearchHelper
|
||||
import com.aurora.gplayapi.helpers.contracts.SearchContract
|
||||
import com.aurora.gplayapi.helpers.web.WebSearchHelper
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SearchSuggestionViewModel @Inject constructor(
|
||||
private val authProvider: AuthProvider,
|
||||
private val searchHelper: SearchHelper,
|
||||
private val webSearchHelper: WebSearchHelper
|
||||
) : ViewModel() {
|
||||
|
||||
private val _searchSuggestions = MutableStateFlow<List<SearchSuggestEntry>>(emptyList())
|
||||
val searchSuggestions = _searchSuggestions.asStateFlow()
|
||||
|
||||
private val helper: SearchContract
|
||||
get() = if (authProvider.isAnonymous) webSearchHelper else searchHelper
|
||||
|
||||
fun observeStreamBundles(query: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_searchSuggestions.value = helper.searchSuggestions(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,20 +20,16 @@
|
||||
package com.aurora.store.viewmodel.search
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.aurora.gplayapi.SearchSuggestEntry
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.SearchHelper
|
||||
import com.aurora.gplayapi.helpers.contracts.SearchContract
|
||||
import com.aurora.gplayapi.helpers.web.WebSearchHelper
|
||||
import com.aurora.store.AppStreamStash
|
||||
import com.aurora.store.data.model.ViewState
|
||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.createPager
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -44,8 +40,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -57,10 +51,6 @@ class SearchViewModel @Inject constructor(
|
||||
|
||||
private val TAG = SearchViewModel::class.java.simpleName
|
||||
|
||||
val liveData: MutableLiveData<ViewState> = MutableLiveData()
|
||||
|
||||
private val stash: AppStreamStash = mutableMapOf()
|
||||
|
||||
private val contract: SearchContract
|
||||
get() = if (authProvider.isAnonymous) webSearchHelper else searchHelper
|
||||
|
||||
@@ -70,9 +60,7 @@ class SearchViewModel @Inject constructor(
|
||||
private val _apps = MutableStateFlow<PagingData<App>>(PagingData.empty())
|
||||
val apps = _apps.asStateFlow()
|
||||
|
||||
private val stashMutex = Mutex()
|
||||
|
||||
fun newSearch(query: String) {
|
||||
fun search(query: String) {
|
||||
var nextBundleUrl: String? = null
|
||||
val nextStreamUrls = mutableSetOf<String>()
|
||||
|
||||
@@ -128,138 +116,4 @@ class SearchViewModel @Inject constructor(
|
||||
_suggestions.value = contract.searchSuggestions(query)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun search(query: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
stashMutex.withLock {
|
||||
liveData.postValue(ViewState.Loading)
|
||||
|
||||
var bundle = targetBundle(query)
|
||||
|
||||
// Post existing data if any clusters exist
|
||||
if (bundle.hasCluster()) {
|
||||
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Fetch new stream bundle
|
||||
val newBundle = contract.searchResults(query)
|
||||
|
||||
bundle = bundle.copy(
|
||||
streamClusters = newBundle.streamClusters,
|
||||
streamNextPageUrl = newBundle.streamNextPageUrl
|
||||
)
|
||||
|
||||
stash[query] = bundle
|
||||
|
||||
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
liveData.postValue(ViewState.Error(e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun observe(query: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
stashMutex.withLock {
|
||||
var bundle = targetBundle(query)
|
||||
|
||||
if (bundle.hasNext()) {
|
||||
val newBundle = contract.nextStreamBundle(
|
||||
query,
|
||||
bundle.streamNextPageUrl
|
||||
)
|
||||
|
||||
// Update old bundle
|
||||
bundle = bundle.copy(
|
||||
streamClusters = bundle.streamClusters + newBundle.streamClusters,
|
||||
streamNextPageUrl = newBundle.streamNextPageUrl
|
||||
)
|
||||
|
||||
stash[query] = bundle
|
||||
|
||||
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||
} else {
|
||||
Log.i(TAG, "End of Bundle")
|
||||
|
||||
// If stream ends, likely there are clusters that need to be processed
|
||||
bundle.streamClusters.values.forEach {
|
||||
if (it.clusterNextPageUrl.isEmpty()) {
|
||||
postClusterEnd(query, it.id)
|
||||
}
|
||||
|
||||
// Empty title or query as title indicates main stream cluster
|
||||
if (it.clusterTitle.isEmpty() or (it.clusterTitle == bundle.streamTitle)) {
|
||||
observeCluster(query, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
liveData.postValue(ViewState.Error(e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun observeCluster(query: String, streamCluster: StreamCluster) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
if (streamCluster.hasNext()) {
|
||||
val newCluster = contract.nextStreamCluster(
|
||||
query,
|
||||
streamCluster.clusterNextPageUrl
|
||||
)
|
||||
stashMutex.withLock {
|
||||
updateCluster(query, streamCluster.id, newCluster)
|
||||
}
|
||||
|
||||
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||
} else {
|
||||
stashMutex.withLock {
|
||||
postClusterEnd(query, streamCluster.id)
|
||||
}
|
||||
|
||||
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
liveData.postValue(ViewState.Error(e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCluster(query: String, clusterID: Int, newCluster: StreamCluster) {
|
||||
val bundle = stash[query] ?: return
|
||||
val oldCluster = bundle.streamClusters[clusterID] ?: return
|
||||
|
||||
val mergedCluster = oldCluster.copy(
|
||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||
)
|
||||
|
||||
val updatedClusters = bundle.streamClusters.toMutableMap().apply {
|
||||
this[clusterID] = mergedCluster
|
||||
}
|
||||
|
||||
stash[query] = bundle.copy(streamClusters = updatedClusters)
|
||||
}
|
||||
|
||||
private fun postClusterEnd(query: String, clusterID: Int) {
|
||||
val bundle = stash[query] ?: return
|
||||
val oldCluster = bundle.streamClusters[clusterID] ?: return
|
||||
|
||||
val updatedCluster = oldCluster.copy(clusterNextPageUrl = "")
|
||||
val updatedClusters = bundle.streamClusters.toMutableMap().apply {
|
||||
this[clusterID] = updatedCluster
|
||||
}
|
||||
|
||||
stash[query] = bundle.copy(streamClusters = updatedClusters)
|
||||
}
|
||||
|
||||
private fun targetBundle(query: String): StreamBundle {
|
||||
return stash.getOrPut(query.trim()) { StreamBundle(streamTitle = query) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user