diff --git a/storybook/pages/SimpleSendModalPage.qml b/storybook/pages/SimpleSendModalPage.qml index 61873d37fca..ba51aed072c 100644 --- a/storybook/pages/SimpleSendModalPage.qml +++ b/storybook/pages/SimpleSendModalPage.qml @@ -37,6 +37,16 @@ SplitView { readonly property WalletAssetsStoreMock walletAssetStore: WalletAssetsStoreMock { walletTokensStore: TokensStoreMock { tokenGroupsModel: TokenGroupsModel{} + tokenGroupsForChainModel: TokenGroupsModel { + skipInitialLoad: true + } + searchResultModel: TokenGroupsModel { + skipInitialLoad: true + fetchMode: true + fetchBatchSize: 8 + fetchInitialCount: 8 + tokenGroupsForChainModel: d.walletAssetStore.walletTokensStore.tokenGroupsForChainModel + } _displayAssetsBelowBalanceThresholdDisplayAmountFunc: () => 0 } } @@ -167,6 +177,13 @@ SplitView { onReviewSendClicked: console.log("Review send clicked") onLaunchBuyFlow: console.log("launch buy flow clicked") + onSearchInAssets: (keyword) => { + if (assetsSelectorViewAdaptor.searchString === "" && keyword !== "") { + d.walletAssetStore.walletTokensStore.buildGroupsForChain(simpleSend.selectedChainId) + } + assetsSelectorViewAdaptor.search(keyword) + } + onFetchMoreAssets: assetsSelectorViewAdaptor.loadMoreItems() Binding on selectedAccountAddress { value: accountsCombobox.currentValue ?? "" @@ -219,6 +236,7 @@ SplitView { accountAddress: simpleSend.selectedAccountAddress enabledChainIds: [simpleSend.selectedChainId] + searchResultModel: d.walletAssetStore.walletTokensStore.searchResultModel } CollectiblesSelectionAdaptor { diff --git a/storybook/src/Models/TokenGroupsModel.qml b/storybook/src/Models/TokenGroupsModel.qml index 81bc53e29da..9fee8b077f5 100644 --- a/storybook/src/Models/TokenGroupsModel.qml +++ b/storybook/src/Models/TokenGroupsModel.qml @@ -394,22 +394,76 @@ ListModel { ] property bool skipInitialLoad: false + property bool fetchMode: false + property int fetchBatchSize: 15 + property int fetchInitialCount: fetchBatchSize + + property bool hasMoreItems: false + property bool isLoadingMore: false + property string searchKeyword: "" + + property var tokenGroupsForChainModel // used for search only + + property var _filteredItems: [] Component.onCompleted: { if (!skipInitialLoad) { - append(data) + _filteredItems = data.slice() + _updateVisibleItems(Math.max(fetchInitialCount, 0)) } } - property bool hasMoreItems: false - property bool isLoadingMore: false + function _relevanceScore(item, keywordLower) { + if (!keywordLower) { + return 0 + } - property var tokenGroupsForChainModel // used for search only + const symbol = (item.symbol || "").toLowerCase() + const name = (item.name || "").toLowerCase() + const key = (item.key || "").toLowerCase() + + if (symbol === keywordLower) + return 100 + if (name === keywordLower) + return 95 + if (symbol.startsWith(keywordLower)) + return 90 + if (name.startsWith(keywordLower)) + return 85 + + const symbolIndex = symbol.indexOf(keywordLower) + if (symbolIndex > 0) + return 70 - Math.min(symbolIndex, 20) + + const nameIndex = name.indexOf(keywordLower) + if (nameIndex > 0) + return 60 - Math.min(nameIndex, 20) + + const keyIndex = key.indexOf(keywordLower) + if (keyIndex === 0) + return 50 + if (keyIndex > 0) + return 40 - Math.min(keyIndex, 20) + + return -1 + } + + function _updateVisibleItems(maxItemCount) { + clear() + const targetCount = fetchMode ? Math.min(maxItemCount, _filteredItems.length) : _filteredItems.length + for (let i = 0; i < targetCount; i++) { + append(_filteredItems[i]) + } + hasMoreItems = count < _filteredItems.length + } function search(keyword) { - clear() // clear the existing model + searchKeyword = (keyword || "").trim() + _filteredItems = [] + clear() + hasMoreItems = false - if (!keyword || keyword.trim() === "") { + if (searchKeyword === "") { return } @@ -418,17 +472,38 @@ ListModel { return } - const lowerKeyword = keyword.toLowerCase() + const keywordLower = searchKeyword.toLowerCase() + const scoredResults = [] for (let i = 0; i < tokenGroupsForChainModel.ModelCount.count; i++) { const item = ModelUtils.get(tokenGroupsForChainModel, i) - const symbolMatch = item.symbol && item.symbol.toLowerCase().includes(lowerKeyword) - const nameMatch = item.name && item.name.toLowerCase().includes(lowerKeyword) - if (symbolMatch || nameMatch) { - append(item) + const score = _relevanceScore(item, keywordLower) + if (score >= 0) { + scoredResults.push({ + score: score, + index: i, + item: item + }) } } + + scoredResults.sort((a, b) => { + if (a.score !== b.score) + return b.score - a.score + return a.index - b.index + }) + + _filteredItems = scoredResults.map(entry => entry.item) + _updateVisibleItems(Math.max(fetchInitialCount, 0)) } function fetchMore() { + if (!fetchMode || isLoadingMore || !hasMoreItems) { + return + } + + isLoadingMore = true + const nextVisibleCount = count + Math.max(fetchBatchSize, 1) + _updateVisibleItems(nextVisibleCount) + isLoadingMore = false } }