Updated on 2026-08-14
This commit is contained in:
parent
b74acabcc4
commit
4649abbb60
21 changed files with 627 additions and 0 deletions
|
|
@ -161,6 +161,7 @@ dependencies {
|
|||
implementation(projects.domain.news)
|
||||
implementation(projects.domain.earn)
|
||||
implementation(projects.domain.tokensync)
|
||||
implementation(projects.domain.search)
|
||||
|
||||
implementation(projects.common)
|
||||
implementation(projects.common.routing)
|
||||
|
|
@ -216,6 +217,7 @@ dependencies {
|
|||
implementation(projects.data.hotWallet)
|
||||
implementation(projects.data.news)
|
||||
implementation(projects.data.earn)
|
||||
implementation(projects.data.search)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.referral.impl)
|
||||
|
|
|
|||
1
data/search/.gitignore
vendored
Normal file
1
data/search/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
40
data/search/build.gradle.kts
Normal file
40
data/search/build.gradle.kts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.search"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// region Project - Core
|
||||
implementation(projects.core.datasource)
|
||||
api(projects.core.utils)
|
||||
// endregion
|
||||
|
||||
// region Project - Data
|
||||
implementation(projects.data.common)
|
||||
// endregion
|
||||
|
||||
// region Project - Domain
|
||||
implementation(projects.domain.search)
|
||||
implementation(projects.domain.common)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.markets.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.appCurrency)
|
||||
// endregion
|
||||
|
||||
// region DI
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
// endregion
|
||||
|
||||
// region Other libraries
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.moshi.kotlin)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.data.search.converter
|
||||
|
||||
import com.tangem.data.search.model.RecentTokenDTO
|
||||
import com.tangem.data.search.model.TextHintDTO
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.search.model.RecentSearchToken
|
||||
import com.tangem.domain.search.model.SearchTextHint
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class TextHintDTOToSearchTextHintConverter : Converter<TextHintDTO, SearchTextHint> {
|
||||
override fun convert(value: TextHintDTO): SearchTextHint {
|
||||
return SearchTextHint(
|
||||
text = value.text,
|
||||
timestamp = value.timestamp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class RecentTokenDTOToRecentSearchTokenConverter : Converter<RecentTokenDTO, RecentSearchToken> {
|
||||
override fun convert(value: RecentTokenDTO): RecentSearchToken {
|
||||
return RecentSearchToken(
|
||||
id = CryptoCurrency.RawID(value.id),
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
imageUrl = value.imageUrl,
|
||||
timestamp = value.timestamp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class RecentSearchTokenToRecentTokenDTOConverter : Converter<RecentSearchToken, RecentTokenDTO> {
|
||||
override fun convert(value: RecentSearchToken): RecentTokenDTO {
|
||||
return RecentTokenDTO(
|
||||
id = value.id.value,
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
imageUrl = value.imageUrl,
|
||||
timestamp = value.timestamp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.tangem.data.search.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import com.tangem.data.search.model.SearchHistoryDTO
|
||||
import com.tangem.data.search.repository.DefaultSearchRepository
|
||||
import com.tangem.data.search.store.DefaultSearchHistoryStore
|
||||
import com.tangem.data.search.store.SearchHistoryStore
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.search.repository.SearchRepository
|
||||
import com.tangem.domain.search.usecase.ClearSearchHistoryUseCase
|
||||
import com.tangem.domain.search.usecase.GetSearchResultsUseCase
|
||||
import com.tangem.domain.search.usecase.SaveRecentSearchTokenUseCase
|
||||
import com.tangem.domain.search.usecase.SaveSearchQueryUseCase
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object SearchDataModule {
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSearchHistoryDataStore(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
appScope: AppCoroutineScope,
|
||||
): DataStore<SearchHistoryDTO> {
|
||||
return DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
defaultValue = SearchHistoryDTO(),
|
||||
adapter = moshi.adapter<SearchHistoryDTO>(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "search_history") },
|
||||
scope = appScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSearchHistoryStore(dataStore: DataStore<SearchHistoryDTO>): SearchHistoryStore {
|
||||
return DefaultSearchHistoryStore(dataStore = dataStore)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSearchRepository(
|
||||
store: SearchHistoryStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): SearchRepository {
|
||||
return DefaultSearchRepository(
|
||||
store = store,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideGetSearchResultsUseCase(
|
||||
searchRepository: SearchRepository,
|
||||
multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
): GetSearchResultsUseCase {
|
||||
return GetSearchResultsUseCase(
|
||||
searchRepository = searchRepository,
|
||||
multiAccountStatusListSupplier = multiAccountStatusListSupplier,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideSaveSearchQueryUseCase(searchRepository: SearchRepository): SaveSearchQueryUseCase {
|
||||
return SaveSearchQueryUseCase(searchRepository = searchRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideSaveRecentSearchTokenUseCase(searchRepository: SearchRepository): SaveRecentSearchTokenUseCase {
|
||||
return SaveRecentSearchTokenUseCase(searchRepository = searchRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideClearSearchHistoryUseCase(searchRepository: SearchRepository): ClearSearchHistoryUseCase {
|
||||
return ClearSearchHistoryUseCase(searchRepository = searchRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.data.search.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class SearchHistoryDTO(
|
||||
val textHints: List<TextHintDTO> = emptyList(),
|
||||
val recentTokens: List<RecentTokenDTO> = emptyList(),
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class TextHintDTO(
|
||||
val text: String,
|
||||
val timestamp: Long,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class RecentTokenDTO(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val imageUrl: String?,
|
||||
val timestamp: Long,
|
||||
)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.data.search.repository
|
||||
|
||||
import com.tangem.data.search.converter.RecentSearchTokenToRecentTokenDTOConverter
|
||||
import com.tangem.data.search.converter.RecentTokenDTOToRecentSearchTokenConverter
|
||||
import com.tangem.data.search.converter.TextHintDTOToSearchTextHintConverter
|
||||
import com.tangem.data.search.model.TextHintDTO
|
||||
import com.tangem.data.search.store.SearchHistoryStore
|
||||
import com.tangem.domain.search.model.RecentSearchToken
|
||||
import com.tangem.domain.search.model.SearchTextHint
|
||||
import com.tangem.domain.search.repository.SearchRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultSearchRepository(
|
||||
private val store: SearchHistoryStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SearchRepository {
|
||||
|
||||
private val textHintConverter by lazy {
|
||||
TextHintDTOToSearchTextHintConverter()
|
||||
}
|
||||
private val recentTokenConverter by lazy {
|
||||
RecentTokenDTOToRecentSearchTokenConverter()
|
||||
}
|
||||
private val recentTokenDMConverter by lazy {
|
||||
RecentSearchTokenToRecentTokenDTOConverter()
|
||||
}
|
||||
|
||||
override fun getTextHints(): Flow<List<SearchTextHint>> {
|
||||
return store.getTextHints()
|
||||
.map { textHintConverter.convertList(it) }
|
||||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
override fun getRecentTokens(): Flow<List<RecentSearchToken>> {
|
||||
return store.getRecentTokens()
|
||||
.map { recentTokenConverter.convertList(it) }
|
||||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
override suspend fun saveTextHint(text: String) = withContext(dispatchers.io) {
|
||||
store.saveTextHint(
|
||||
TextHintDTO(
|
||||
text = text,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun saveRecentToken(token: RecentSearchToken) = withContext(dispatchers.io) {
|
||||
store.saveRecentToken(recentTokenDMConverter.convert(token))
|
||||
}
|
||||
|
||||
override suspend fun clearHistory() = withContext(dispatchers.io) {
|
||||
store.clearAll()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.data.search.store
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.data.search.model.RecentTokenDTO
|
||||
import com.tangem.data.search.model.SearchHistoryDTO
|
||||
import com.tangem.data.search.model.TextHintDTO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultSearchHistoryStore(
|
||||
private val dataStore: DataStore<SearchHistoryDTO>,
|
||||
) : SearchHistoryStore {
|
||||
|
||||
override fun getTextHints(): Flow<List<TextHintDTO>> {
|
||||
return dataStore.data.map { it.textHints.sortedByDescending(TextHintDTO::timestamp) }
|
||||
}
|
||||
|
||||
override fun getRecentTokens(): Flow<List<RecentTokenDTO>> {
|
||||
return dataStore.data.map { it.recentTokens.sortedByDescending(RecentTokenDTO::timestamp) }
|
||||
}
|
||||
|
||||
override suspend fun saveTextHint(hint: TextHintDTO) {
|
||||
dataStore.updateData { current ->
|
||||
val updated = current.textHints
|
||||
.filter { it.text != hint.text }
|
||||
.toMutableList()
|
||||
.apply { add(0, hint) }
|
||||
.take(MAX_HISTORY_SIZE)
|
||||
current.copy(textHints = updated)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveRecentToken(token: RecentTokenDTO) {
|
||||
dataStore.updateData { current ->
|
||||
val updated = current.recentTokens
|
||||
.filter { it.id != token.id }
|
||||
.toMutableList()
|
||||
.apply { add(0, token) }
|
||||
.take(MAX_HISTORY_SIZE)
|
||||
current.copy(recentTokens = updated)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearAll() {
|
||||
dataStore.updateData {
|
||||
SearchHistoryDTO()
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_HISTORY_SIZE = 3
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.data.search.store
|
||||
|
||||
import com.tangem.data.search.model.RecentTokenDTO
|
||||
import com.tangem.data.search.model.TextHintDTO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
internal interface SearchHistoryStore {
|
||||
fun getTextHints(): Flow<List<TextHintDTO>>
|
||||
fun getRecentTokens(): Flow<List<RecentTokenDTO>>
|
||||
suspend fun saveTextHint(hint: TextHintDTO)
|
||||
suspend fun saveRecentToken(token: RecentTokenDTO)
|
||||
suspend fun clearAll()
|
||||
}
|
||||
1
domain/search/.gitignore
vendored
Normal file
1
domain/search/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
20
domain/search/build.gradle.kts
Normal file
20
domain/search/build.gradle.kts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.domain.search"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.domain.core)
|
||||
api(projects.domain.models)
|
||||
implementation(projects.domain.common)
|
||||
implementation(projects.domain.markets.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.domain.search.model
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
||||
/**
|
||||
* @property timestamp epoch milliseconds
|
||||
*/
|
||||
data class RecentSearchToken(
|
||||
val id: CryptoCurrency.RawID,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val imageUrl: String?,
|
||||
val timestamp: Long,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.search.model
|
||||
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
|
||||
data class SearchResult(
|
||||
val textHints: List<SearchTextHint>,
|
||||
val recentTokens: List<RecentSearchToken>,
|
||||
val userAssets: List<UserAssetSearchEntry>,
|
||||
val marketTokens: List<TokenMarket>,
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.search.model
|
||||
|
||||
/**
|
||||
* @property timestamp epoch milliseconds
|
||||
*/
|
||||
data class SearchTextHint(val text: String, val timestamp: Long)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.domain.search.model
|
||||
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
data class UserAssetSearchEntry(
|
||||
val userWalletId: UserWalletId,
|
||||
val userWalletName: String,
|
||||
val accountId: AccountId,
|
||||
val accountName: AccountName,
|
||||
val currencyStatus: CryptoCurrencyStatus,
|
||||
)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.domain.search.repository
|
||||
|
||||
import com.tangem.domain.search.model.RecentSearchToken
|
||||
import com.tangem.domain.search.model.SearchTextHint
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Repository responsible for managing local search history storage.
|
||||
* Handles persistence of user's past search queries and recently viewed market tokens.
|
||||
* Each history type is limited to 3 entries, sorted by timestamp in descending order.
|
||||
*/
|
||||
interface SearchRepository {
|
||||
|
||||
/** Observes the list of saved text hints, sorted by timestamp descending. */
|
||||
fun getTextHints(): Flow<List<SearchTextHint>>
|
||||
|
||||
/** Observes the list of recently viewed market tokens, sorted by timestamp descending. */
|
||||
fun getRecentTokens(): Flow<List<RecentSearchToken>>
|
||||
|
||||
/**
|
||||
* Saves a text hint to the search history.
|
||||
* If the hint already exists, its timestamp is updated. Oldest entries are evicted when the limit is exceeded.
|
||||
*
|
||||
* @param text the search query text to save
|
||||
*/
|
||||
suspend fun saveTextHint(text: String)
|
||||
|
||||
/**
|
||||
* Saves a recently viewed market token to the search history.
|
||||
* If a token with the same ID already exists, it is moved to the top. Oldest entries are evicted when the limit
|
||||
* is exceeded.
|
||||
*
|
||||
* @param token the market token entry to save
|
||||
*/
|
||||
suspend fun saveRecentToken(token: RecentSearchToken)
|
||||
|
||||
/** Clears all search history, including both text hints and recent tokens. */
|
||||
suspend fun clearHistory()
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.search.usecase
|
||||
|
||||
import com.tangem.domain.search.repository.SearchRepository
|
||||
|
||||
/**
|
||||
* Clears the entire search history, removing both text hints and recently viewed tokens.
|
||||
*
|
||||
* @property searchRepository local search history storage
|
||||
*/
|
||||
class ClearSearchHistoryUseCase(
|
||||
private val searchRepository: SearchRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke() {
|
||||
searchRepository.clearHistory()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package com.tangem.domain.search.usecase
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
import com.tangem.domain.models.account.filterCryptoPortfolio
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.search.model.SearchResult
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.domain.search.repository.SearchRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
/**
|
||||
* Primary search use case that produces [SearchResult] based on the current query.
|
||||
*
|
||||
* Behavior depends on the query:
|
||||
* - **Empty query** — returns search history: text hints and recently viewed tokens.
|
||||
* - **Non-empty query** — performs the search across all unlocked user wallets,
|
||||
* matching currencies by name or symbol, and combines the results with externally provided market tokens.
|
||||
*
|
||||
* @property searchRepository local search history storage
|
||||
* @property multiAccountStatusListSupplier supplier for loaded account status lists across all wallets
|
||||
* @property userWalletsListRepository repository providing the list of user wallets
|
||||
*/
|
||||
class GetSearchResultsUseCase(
|
||||
private val searchRepository: SearchRepository,
|
||||
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Produces a [Flow] of [SearchResult] for the given [query].
|
||||
*
|
||||
* @param query the search query string; blank means "show history"
|
||||
* @param marketTokens external flow of market token search results (provided by presentation layer)
|
||||
*/
|
||||
operator fun invoke(
|
||||
query: String,
|
||||
marketTokens: Flow<List<TokenMarket>> = flowOf(emptyList()),
|
||||
): Flow<SearchResult> {
|
||||
return if (query.isBlank()) {
|
||||
observeHistory()
|
||||
} else {
|
||||
searchAssets(query, marketTokens)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeHistory(): Flow<SearchResult> {
|
||||
return combine(
|
||||
searchRepository.getTextHints(),
|
||||
searchRepository.getRecentTokens(),
|
||||
) { hints, tokens ->
|
||||
SearchResult(
|
||||
textHints = hints,
|
||||
recentTokens = tokens,
|
||||
userAssets = emptyList(),
|
||||
marketTokens = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchAssets(query: String, marketTokens: Flow<List<TokenMarket>>): Flow<SearchResult> {
|
||||
return combine(
|
||||
observeUserAssets(query),
|
||||
marketTokens,
|
||||
) { userAssets, markets ->
|
||||
SearchResult(
|
||||
textHints = emptyList(),
|
||||
recentTokens = emptyList(),
|
||||
userAssets = userAssets,
|
||||
marketTokens = markets,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeUserAssets(query: String): Flow<List<UserAssetSearchEntry>> {
|
||||
val lowerQuery = query.lowercase()
|
||||
return combine(
|
||||
multiAccountStatusListSupplier(),
|
||||
userWalletsListRepository.userWallets,
|
||||
) { statusLists, wallets ->
|
||||
val unlockedWallets = wallets
|
||||
.orEmpty()
|
||||
.filterNot(UserWallet::isLocked)
|
||||
.associateBy { it.walletId }
|
||||
|
||||
if (unlockedWallets.isEmpty()) return@combine emptyList()
|
||||
|
||||
statusLists
|
||||
.filter { it.userWalletId in unlockedWallets }
|
||||
.flatMap { statusList -> extractMatchingAssets(statusList, unlockedWallets, lowerQuery) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractMatchingAssets(
|
||||
statusList: AccountStatusList,
|
||||
wallets: Map<UserWalletId, UserWallet>,
|
||||
lowerQuery: String,
|
||||
): List<UserAssetSearchEntry> {
|
||||
val wallet = wallets[statusList.userWalletId] ?: return emptyList()
|
||||
return statusList.accountStatuses
|
||||
.filterCryptoPortfolio()
|
||||
.flatMap { accountStatus ->
|
||||
accountStatus.flattenCurrencies()
|
||||
.filter { currencyStatus ->
|
||||
val name = currencyStatus.currency.name.lowercase()
|
||||
val symbol = currencyStatus.currency.symbol.lowercase()
|
||||
name.contains(lowerQuery) || symbol.contains(lowerQuery)
|
||||
}
|
||||
.map { currencyStatus ->
|
||||
UserAssetSearchEntry(
|
||||
userWalletId = statusList.userWalletId,
|
||||
userWalletName = wallet.name,
|
||||
accountId = accountStatus.accountId,
|
||||
accountName = accountStatus.account.accountName,
|
||||
currencyStatus = currencyStatus,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.search.usecase
|
||||
|
||||
import com.tangem.domain.search.model.RecentSearchToken
|
||||
import com.tangem.domain.search.repository.SearchRepository
|
||||
|
||||
/**
|
||||
* Saves a market token to the "recently viewed" search history.
|
||||
* Only market assets should be saved (user's own assets are ignored).
|
||||
* The history is limited to 3 entries; oldest entries are evicted automatically.
|
||||
*
|
||||
* @property searchRepository local search history storage
|
||||
*/
|
||||
class SaveRecentSearchTokenUseCase(
|
||||
private val searchRepository: SearchRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* @param token the market token to persist as a recent search entry
|
||||
*/
|
||||
suspend operator fun invoke(token: RecentSearchToken) {
|
||||
searchRepository.saveRecentToken(token)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.search.usecase
|
||||
|
||||
import com.tangem.domain.search.repository.SearchRepository
|
||||
|
||||
/**
|
||||
* Saves the current search query text to the local search history.
|
||||
* Blank queries are ignored. The history is limited to 3 entries; oldest entries are evicted automatically.
|
||||
* Should be called when the user selects any asset from the search results.
|
||||
*
|
||||
* @property searchRepository local search history storage
|
||||
*/
|
||||
class SaveSearchQueryUseCase(
|
||||
private val searchRepository: SearchRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* @param query the search text to persist; blank values are silently ignored
|
||||
*/
|
||||
suspend operator fun invoke(query: String) {
|
||||
if (query.isBlank()) return
|
||||
searchRepository.saveTextHint(query.trim())
|
||||
}
|
||||
}
|
||||
|
|
@ -392,6 +392,7 @@ include(":domain:yield-supply")
|
|||
include(":domain:yield-supply:models")
|
||||
include(":domain:news")
|
||||
include(":domain:earn")
|
||||
include(":domain:search")
|
||||
// endregion Domain modules
|
||||
|
||||
// region Data modules
|
||||
|
|
@ -430,4 +431,5 @@ include(":data:wallet-manager")
|
|||
include(":data:yield-supply")
|
||||
include(":data:news")
|
||||
include(":data:earn")
|
||||
include(":data:search")
|
||||
// endregion Data modules
|
||||
Loading…
Add table
Add a link
Reference in a new issue