Skip to content

Consilidate wallet search#477

Merged
gemdev111 merged 37 commits into
mainfrom
consilidate-wallet-search
Jun 16, 2026
Merged

Consilidate wallet search#477
gemdev111 merged 37 commits into
mainfrom
consilidate-wallet-search

Conversation

@gemdev111

@gemdev111 gemdev111 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

closes #354

images:

Browse
tags · recents · perpetuals · assets
Pinned
perpetual + asset, one section
Search "btc"
ranked perps + assets
1-browse 2-browse-pinned 3-search-btc-perps+assets
Contract search
token id → Tether
Perpetuals ›
market screen
Assets ›
read-only, capped 100
4-contract-search-tether 5-perpetuals-market 6-assets-results
Perpetual detail
opened from search
Empty state
no results → Add Custom Token
7-perpetual-detail 8-browse-empty

gemdev111 added 6 commits June 9, 2026 14:33
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.
@gemdev111 gemdev111 self-assigned this Jun 9, 2026
gemdev111 added 7 commits June 9, 2026 20:34
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.
@gemdev111 gemdev111 marked this pull request as ready for review June 10, 2026 09:16
# 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
perpetuals.map { PerpetualData(perpetual = it.perpetual, asset = it.asset, metadata = PerpetualMetadata(isPinned = false)) }
)
searchPriorityDao.put(perpetuals.toSearchPriority(priorityQuery))
} catch (err: CancellationException) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too much } catch (err: CancellationException) {
throw err handling,

@Test
fun search_ingestsPerpetualsAndStoresPerpPriority() = runTest {
val perpAsset = mockAsset()
val perpetual = Perpetual(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use mock() for perp

import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless unit test

override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("DROP TABLE IF EXISTS `assets_priority`")
db.execSQL(
"CREATE TABLE IF NOT EXISTS `search_priority` (" +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this consistent with ios naming?

searchable = false,
onChainFilter = {},
onBalanceFilter = {},
onClearFilters = {},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move those limits to gemstone? so it's reused for ios and android

gemdev111 added 13 commits June 11, 2026 22:58
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.
gemdev111 added 10 commits June 15, 2026 21:01
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.
@gemdev111 gemdev111 merged commit 7d12ade into main Jun 16, 2026
6 checks passed
@gemdev111 gemdev111 deleted the consilidate-wallet-search branch June 16, 2026 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consolidate global search

2 participants