Consilidate wallet search#477
Merged
Merged
Conversation
Introduce a single search_priority table keyed by (query, type, item_id) to replace assets_priority, with Migration 79->80 and the exported schema.
Rank results via search_priority joins and match assets by contract/tokenId in local asset search.
Expose the unified /v1/search endpoint returning assets and perpetuals behind a GemSearch coordinator alongside SearchAssets.
Fetch via GemSearch and persist assets, perpetuals, and ranking; wire repositories, DI, and showPerpetuals gating.
Make assetRows public, add getAssetBadge, and support clickable section headers via nullable SubheaderItem onClick.
Port the iOS WalletSearchScene/AssetsResultsScene flow — sections, preview limits, onAction routing, perpetuals gating — and replace the old AssetsSearchScreen.
Pinned perpetuals and pinned assets share the Pinned header but were positioned independently, so a single pinned perpetual plus a single pinned asset rendered as two separate rounded cards instead of one joined card like iOS. Position both sub-lists against the combined count via itemsPositioned indexOffset/totalCount, and drop the now-unused getListPosition extension.
hasPriorities() is a Room flow that re-emits on every search_priority write, and the flatMapLatest keyed on it lacked distinctUntilChanged, so a repeated search for the same query restarted the inner query and reloaded the list. Add distinctUntilChanged across asset, swap, and perpetual search; priority ranking still updates reactively through the JOIN.
The network token search hung off the 5-input filters flow, firing on session/filter churn rather than only query/tag and without a real debounce, so fast typing launched overlapping searches racing on the no-results state. Drive it off a distinct (query, tag, currency, chains) stream with collectLatest, matching iOS which searches only on user input. Gate the trigger behind remoteSearch and disable it on the AssetsResults drill-down, which re-fetched results already in the store and rewrote search_priority, forcing a reload. It now renders straight from the store like iOS's read-only results view model.
Catch blocks on the token search path rethrow CancellationException so a search cancelled by a newer query under collectLatest stops instead of completing as a failure and writing a stale no-results marker. Move the remoteSearch toggle from an open property read in the base class init, a virtual call during construction, to a constructor parameter.
Only wallet search reset the tag when the user typed; Send, Buy, Swap, and price alert pickers kept it applied invisibly (the tag bar hides while typing), filtered results by query::tag so the local LIKE fallback matched nothing, and restored the stale tag when the query cleared. iOS resets the tag on search focus in every picker via SelectAssetViewModel.onChangeFocus. Move the rule from WalletSearchViewModel into the shared base.
# Conflicts: # android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepository.kt # android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/di/AssetsModule.kt # android/data/repositories/src/test/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepositoryTest.kt
gemcoder21
approved these changes
Jun 11, 2026
| perpetuals.map { PerpetualData(perpetual = it.perpetual, asset = it.asset, metadata = PerpetualMetadata(isPinned = false)) } | ||
| ) | ||
| searchPriorityDao.put(perpetuals.toSearchPriority(priorityQuery)) | ||
| } catch (err: CancellationException) { |
Contributor
There was a problem hiding this comment.
too much } catch (err: CancellationException) {
throw err handling,
| @Test | ||
| fun search_ingestsPerpetualsAndStoresPerpPriority() = runTest { | ||
| val perpAsset = mockAsset() | ||
| val perpetual = Perpetual( |
| import org.junit.Test | ||
| import org.junit.runner.RunWith | ||
|
|
||
| @RunWith(AndroidJUnit4::class) |
| override fun migrate(db: SupportSQLiteDatabase) { | ||
| db.execSQL("DROP TABLE IF EXISTS `assets_priority`") | ||
| db.execSQL( | ||
| "CREATE TABLE IF NOT EXISTS `search_priority` (" + |
Contributor
There was a problem hiding this comment.
is this consistent with ios naming?
| searchable = false, | ||
| onChainFilter = {}, | ||
| onBalanceFilter = {}, | ||
| onClearFilters = {}, |
Contributor
There was a problem hiding this comment.
too many methods? can we create onAction(action), and action is a enum? we have some examples
| @@ -0,0 +1,9 @@ | |||
| package com.gemwallet.android.features.assets.viewmodels | |||
|
|
|||
| object WalletSearchLimits { | |||
Contributor
There was a problem hiding this comment.
can we move those limits to gemstone? so it's reused for ios and android
iOS stores per-query ranking in a table named search (SearchRecord); use the same table name on Android per the store schema naming rule. Schema 80.json identity hash refreshes on the next Room build.
Replaces the repeated rethrow-cancellation catch blocks with one helper that fails soft on errors but lets coroutine cancellation propagate.
Fold the per-callback lambdas into WalletSearchAction and dispatch through one handler in the screen, mirroring PerpetualMarketAction: view-model actions are handled locally, navigation actions forward to the host.
DbSearch, SearchDao, and toSearchRecord mirror the renamed search table the way DbAsset and AssetsDao mirror theirs, matching iOS SearchRecord.
Shared home alongside the other ext helpers so the hand-rolled cancellation-rethrow blocks elsewhere can adopt it.
# Conflicts: # android/data/services/store/schemas/com.gemwallet.android.data.service.store.database.GemDatabase/80.json # android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_79_80.kt
Replace the Android-only type+item_id discriminator with iOS SearchRecord's nullable assetId/perpetualId foreign-key columns (cascade delete), matching the shared search table shape. DAOs join on the typed column; SearchDao exposes hasAssetPriorities/hasPerpetualPriorities and per-kind delete. v80->81 migration creates the new table; Room regenerates 81.json on build.
WalletSearchTokensTest asserted on the removed type column and TokensRepositoryTest on itemId; assert on perpetualId/assetId instead.
Non-priority asset fallback now orders by pinned then enabled before fiat and rank, matching iOS WalletSearchRequest. storeAssets clears stale asset priorities when the server returns no assets for a query, like iOS SearchStore.addAssets always does.
Wallet search and the assets results screen carry an explicit row limit into the SQL query (preview+1 and the gemstone results limit) instead of fetching every matching row and capping in the view model. Neither screen applies an in-memory chain/balance filter, so the SQL limit is exact there. Every other picker (Send, Buy, Swap, manage, price alerts) still filters chain/balance in memory, so it keeps NO_QUERY_LIMIT and its existing unbounded fetch — capping before that in-memory filter would drop rows. Bringing those pickers fully to iOS parity means moving their filters into SQL, a separate change touching each picker's own query path. Plain LIMIT :limit, no sentinel; the limit flows VM -> SelectAssetFilters -> SearchSelectAssets -> AssetsRepository -> AssetsSearchService -> DAO.
Auto-generated snapshot for the search-table schema (v80->v81). Matches Migration_80_81 byte-for-byte; required for Room's runtime schema check and MigrationTestHelper, consistent with the committed 74-80 exports.
The new search table has its own id primary key (assets_priority did not), so the bare 'id IN (:byAssets)' in the swap search joins became ambiguous against asset_info.id. Qualify both swap queries to asset_info.id.
Per review: replace ~10 on* callbacks on AssetSelectScene with a single onAction(AssetSelectAction) sealed dispatch (mirrors WalletSearchAction/ NftListAction), plus two gating booleans (recentsSheetEnabled, assetsHeaderClickable) for the structural nullable cases. Internals unchanged via adapter lambdas. All 4 callers (WalletSearch, AssetSelect, Manage, Swap) map actions to their handlers. Verified end-to-end on emulator: select, tag, cancel, show-all, recents sheet, chain/balance/clear filters all work.
WalletSearchTokens now writes the /v1/search assets directly (update + search-priority) instead of routing through TokensRepository.storeAssets, so the wallet-search screen no longer issues a redundant on-chain tokenService.search when the gateway returns no assets. Mirrors iOS WalletSearchService, which does a single request with no fallback. The picker path keeps the fallback via storeAssets, unchanged.
Gate remote search by picker type like iOS isNetworkSearchEnabled: Send and Swap-pay search local assets only (no /v1/search call), while Buy, Manage, Swap-receive and price alerts keep network search. Drive the loading indicator off an explicit isSearching flag so a settled search whose results are all filtered out (e.g. a non-owned token in Send) shows the empty state instead of spinning forever, matching iOS showLoading = isLoading && empty.
mockDbAssetInfo did not pass balanceWithdrawable / balanceWithdrawableAmount, which were added to DbAssetInfo, breaking the test-fixtures build and every unit test that depends on it.
Resolve TokensRepository.search conflict: main adopted the parallel api ∥ on-chain merge (searchAssets + tokenService.search, deduped), which matches iOS AssetsService.searchAssets. Keep that structure but on this branch's search-table schema (searchDao.put + toSearchRecord instead of the removed assetsPriorityDao/toRecordPriority), preserve cancellation-safe runCatchingCancellable, and keep stale-priority clearing on empty results. The now-redundant storeAssets fallback is removed. Picker network-search gating (Send/Swap-pay local only) lives in the view models, so the node search only runs for network-enabled pickers, as on iOS.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes #354
images:
tags · recents · perpetuals · assets
perpetual + asset, one section
ranked perps + assets
token id → Tether
market screen
read-only, capped 100
opened from search
no results → Add Custom Token