Feat/merge market pages - #6517
Conversation
There was a problem hiding this comment.
Pull request overview
Merges crypto, perpetual, stock, watchlist, and indicator markets into a unified Compose page.
Changes:
- Adds unified market models, filtering, sorting, settings, and tests.
- Integrates live market, favorite, indicator, and perpetual data.
- Centralizes Gradle dependency and plugin versions.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
settings.gradle.kts |
Centralizes plugin versions. |
build.gradle.kts |
Consolidates dependency versions. |
app/build.gradle.kts |
Uses centralized versions. |
MarketPageModelsTest.kt |
Tests market mapping and sorting. |
strings.xml |
Adds market labels. |
values-zh-rTW/strings.xml |
Adds Traditional Chinese labels. |
values-zh-rCN/strings.xml |
Adds Simplified Chinese labels. |
ic_config.xml |
Adds display-settings icon. |
MultiColorProgressBar.kt |
Supports custom segment colors. |
SwapViewModel.kt |
Routes market access through repository. |
MarketFragment.kt |
Hosts the new Compose market page. |
MarketPageViewModel.kt |
Manages unified market state and refreshes. |
MarketPageModels.kt |
Defines market entries and mapping logic. |
MarketPage.kt |
Implements the unified market UI. |
TokenRepository.kt |
Adds market fetching and favorite observation. |
MarketDao.kt |
Adds reactive favorite-market query. |
Comments suppressed due to low confidence (3)
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:162
- This clickable scanner icon has no accessibility label, so screen readers announce an unlabeled button.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:460
- The favorite control exposes neither a label nor its selected state, so assistive technology cannot identify whether activating it will add or remove the market. Give it a localized action label and toggle/selected semantics based on
entry.isFavored.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:911
- The dialog's close button is unlabeled for screen-reader users.
contentDescription = null,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (oldSettings.priceChangePeriod != settings.priceChangePeriod) { | ||
| refreshMarkets() | ||
| } |
| RxBus.listen(GlobalMarketEvent::class.java) | ||
| .observeOn(AndroidSchedulers.mainThread()) | ||
| .autoDispose(destroyScope) | ||
| .subscribe { _ -> | ||
| marketsAdapter.notifyDataSetChanged() | ||
| watchlistAdapter.notifyDataSetChanged() | ||
| } | ||
| bindData() | ||
| view.viewTreeObserver.addOnGlobalLayoutListener { | ||
| if (view.isShown) { | ||
| if (job?.isActive == true) return@addOnGlobalLayoutListener | ||
| job = lifecycleScope.launch { | ||
| delay(30000) | ||
| updateUI() | ||
| } | ||
| } else { | ||
| job?.cancel() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private fun loadGlobalMarket() { | ||
| try { | ||
| defaultSharedPreferences.getString(PREF_GLOBAL_MARKET, null)?.let { json -> | ||
| GsonHelper.customGson.fromJson(json, GlobalMarket::class.java)?.let { | ||
| binding.apply { | ||
| marketCap.render(R.string.Global_Market_Cap, it.marketCap, BigDecimal(it.marketCapChangePercentage)) | ||
| volume.render(R.string.volume_24h, it.volume, BigDecimal(it.volumeChangePercentage)) | ||
| dominance.render(R.string.Dominance, BigDecimal(it.dominancePercentage), it.dominance) | ||
| } | ||
| } | ||
| } | ||
| } catch (e: Exception) { | ||
| Timber.e(e) | ||
| } | ||
| } | ||
|
|
||
| private var type = MixinApplication.appContext.defaultSharedPreferences.getInt(Constants.Account.PREF_MARKET_TYPE, TYPE_ALL) | ||
| set(value) { | ||
| if (field != value) { | ||
| field = value | ||
| defaultSharedPreferences.putInt(Constants.Account.PREF_MARKET_TYPE, value) | ||
| when (type) { | ||
| TYPE_ALL -> { | ||
| binding.dropTopSort.isVisible = true | ||
| binding.titleLayout.setText(R.string.Market_Cap) | ||
| binding.markets.isVisible = true | ||
| binding.watchlist.isVisible = false | ||
| binding.titleLayout.isVisible = true | ||
| binding.empty.isVisible = false | ||
| } | ||
|
|
||
| else -> { | ||
| binding.dropTopSort.isVisible = false | ||
| binding.titleLayout.setText(R.string.Watchlist) | ||
| binding.markets.isVisible = false | ||
| if (watchlistAdapter.itemCount == 0) { | ||
| binding.titleLayout.isVisible = false | ||
| binding.empty.isVisible = true | ||
| binding.watchlist.isVisible = false | ||
| } else { | ||
| binding.titleLayout.isVisible = true | ||
| binding.empty.isVisible = false | ||
| binding.watchlist.isVisible = true | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private var top = 0 // 0 is top100, 1 is top200, 2 is top500 | ||
| set(value) { | ||
| if (field != value) { | ||
| field = value | ||
| bindData() | ||
| } | ||
| } | ||
|
|
||
| private var lastFiatCurrency: String? = null | ||
|
|
||
| private var currentOrder: MarketSort = MarketSort.RANK_ASCENDING | ||
|
|
||
| private var marketJob: Job? = null | ||
| private var watchlistJob: Job? = null | ||
| private var loadStateJob: Job? = null | ||
|
|
||
| @SuppressLint("NotifyDataSetChanged") | ||
| private fun bindData() { | ||
| val limit = when (top) { | ||
| 1 -> 200 | ||
| 2 -> 500 | ||
| else -> 100 | ||
| } | ||
|
|
||
| binding.dropTopTv.text = getString( | ||
| R.string.top_count, | ||
| when (top) { | ||
| 1 -> 200 | ||
| 2 -> 500 | ||
| else -> 100 | ||
| } | ||
| ) | ||
|
|
||
| binding.dropPercentageTv.text = if (topPercentage == 0) { | ||
| getString(R.string.change_percent_period_day, 7) | ||
| } else { | ||
| getString(R.string.change_percent_period_hour, 24) | ||
| } | ||
|
|
||
| // Cancel previous job if it exists | ||
| marketJob?.cancel() | ||
| watchlistJob?.cancel() | ||
| loadStateJob?.cancel() | ||
|
|
||
| marketJob = viewLifecycleOwner.lifecycleScope.launch { | ||
| walletViewModel.getWeb3Markets(limit, currentOrder).collectLatest { pagingData -> | ||
| marketsAdapter.submitData(pagingData) | ||
| if (lastFiatCurrency != Session.getFiatCurrency()) { | ||
| lastFiatCurrency = Session.getFiatCurrency() | ||
| marketsAdapter.notifyDataSetChanged() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| watchlistJob = viewLifecycleOwner.lifecycleScope.launch { | ||
| walletViewModel.getFavoredWeb3Markets(currentOrder).collectLatest { pagingData -> | ||
| watchlistAdapter.submitData(pagingData) | ||
| if (lastFiatCurrency != Session.getFiatCurrency()) { | ||
| lastFiatCurrency = Session.getFiatCurrency() | ||
| watchlistAdapter.notifyDataSetChanged() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| loadStateJob = viewLifecycleOwner.lifecycleScope.launch { | ||
| watchlistAdapter.loadStateFlow.collectLatest { _ -> | ||
| val isEmpty = watchlistAdapter.itemCount == 0 | ||
| if (isEmpty && type == TYPE_FOV) { | ||
| binding.titleLayout.isVisible = false | ||
| binding.empty.isVisible = true | ||
| binding.watchlist.isVisible = false | ||
| } else if (type == TYPE_FOV) { | ||
| binding.titleLayout.isVisible = true | ||
| binding.empty.isVisible = false | ||
| binding.watchlist.isVisible = true | ||
| } | ||
| } | ||
| } | ||
| .subscribe { viewModel.loadIndicator() } |
| IconButton(onClick = onSearch) { | ||
| Icon( | ||
| painter = painterResource(R.drawable.ic_search_home), | ||
| contentDescription = null, |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (4)
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:155
- This actionable search button has no accessible name, so screen readers announce an unlabeled control.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:460
- The favorite toggle is an actionable icon with no accessible name or state, so assistive-technology users cannot identify what it does. Provide an add/remove-favorite description based on
entry.isFavored.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:911
- The dialog's close button has no accessible name, so screen readers announce an unlabeled control.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageViewModel.kt:117
- Changing the price-change period while a market fetch is active does not actually fetch the new period:
refreshMarkets()returns early at line 141, leaving the UI set to (for example) 24h while the cached lists and sparklines still contain the in-flight 7d response. Ensure a request for the newly selected period is queued after the active request finishes (or make cancellation propagate and restart it safely).
if (oldSettings.priceChangePeriod != settings.priceChangePeriod) {
refreshMarkets()
}
| _uiState.value = | ||
| _uiState.value.copy( | ||
| isLoading = false, | ||
| hasError = results.allFailed, |
| if (period == MarketPriceChangePeriod.SEVEN_DAYS) { | ||
| return markets | ||
| } |
| if (entry.isFavored) { | ||
| AnalyticsTracker.MarketSource.MORE_FAVORITES | ||
| } else { | ||
| AnalyticsTracker.MarketSource.MORE_MARKET_CAP |
| IconButton(onClick = onScan) { | ||
| Icon( | ||
| painter = painterResource(R.drawable.ic_bot_category_scan), | ||
| contentDescription = null, |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (7)
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageModels.kt:134
- The 7-day period is the persisted default, but this branch makes every Perpetual Top Gainers/Top Losers tab return the same unsorted list, while the row renderer also shows
--for every change. Until 7-day perpetual data exists, either hide/disable that period for the Perpetual tab or explicitly fall back to 24-hour values so these tabs remain functional.
if (period == MarketPriceChangePeriod.SEVEN_DAYS) {
return markets
}
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageViewModel.kt:142
- A period change can be dropped here while the initial request is active. The running request keeps the old
duration,applyDisplaySettings()callsrefreshMarkets(), and this early return prevents a request for the new duration; Crypto gainers/losers then remain ordered for the old period until another external refresh. Cancel/restart the old request or queue one refresh with the latest settings.
fun refreshMarkets() {
if (marketRefreshJob?.isActive == true) return
marketRefreshJob =
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageViewModel.kt:177
allFailedmasks failures for the category the user is viewing whenever any unrelated request succeeds. For example, iftrendingfails butallsucceeds, the default Crypto/Trending page is empty and reports “No Markets” instead of a network error; stale category data can likewise survive a duration change. Track loading/error state per category (or derive it for the selected tab) rather than using one aggregate flag.
_uiState.value =
_uiState.value.copy(
isLoading = false,
hasError = results.allFailed,
)
app/src/main/java/one/mixin/android/ui/home/web3/MarketFragment.kt:156
- The analytics source is being inferred from the asset's favorite status rather than the list that was clicked. A favored asset shown under Crypto or Stock is therefore reported as
MORE_FAVORITES, whereas the previous market-list path reportedMORE_MARKET_CAP. Pass the selected top tab/list context into this navigation decision so analytics reflects the actual source.
if (entry.isFavored) {
AnalyticsTracker.MarketSource.MORE_FAVORITES
} else {
AnalyticsTracker.MarketSource.MORE_MARKET_CAP
},
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:155
- This clickable search icon has no accessibility label, so TalkBack announces an unlabeled button. Use the existing
Searchstring as its content description.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:162
- This clickable scan icon has no accessibility label, so screen-reader users cannot identify its action. Use the existing
Scanstring as its content description.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:911
- The dialog's close button is unlabeled for screen readers. The existing localized
Closestring can be used directly.
contentDescription = null,
| R.drawable.ic_asset_favorites | ||
| }, | ||
| ), | ||
| contentDescription = null, |
| } | ||
| Spacer(modifier = Modifier.width(4.dp)) | ||
| SortLabel( | ||
| text = "Vol", |
# Conflicts: # app/build.gradle.kts # build.gradle.kts
Keep spot and perpetual favorites independent while sharing the Markets UI.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (5)
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageViewModel.kt:119
- If a market refresh is already active, this call is ignored, even though that request captured the old duration. Applying 24h while the initial 7d request is running therefore leaves Trending/Gainers/Losers populated from 7d data with no follow-up refresh. Wait for the active job and then refresh using the new period.
if (oldSettings.priceChangePeriod != settings.priceChangePeriod) {
refreshMarkets()
}
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:191
- This icon-only search action has no accessible label, so TalkBack announces an unlabeled button. Use the existing localized Search string as its content description.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:198
- This icon-only scan action has no accessible label, so TalkBack announces an unlabeled button. Use the existing localized Scan string as its content description.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:623
- The favorite
IconButtonhas no content description, leaving its distinct nested action unlabeled to screen-reader users. Provide state-specific “Add to watchlist” / “Remove from watchlist” descriptions.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt:220
- The new favorite action is icon-only and has no content description, so TalkBack cannot identify whether it adds or removes the market. Set a state-specific localized description alongside the image resource.
contentDescription = null,
| val tabs = | ||
| if (topTab == MarketTopTab.WATCHLIST) { | ||
| listOf(MarketSubTab.CRYPTO, MarketSubTab.PERPETUAL) | ||
| } else { |
| is MarketListEntry.Spot -> | ||
| viewModelScope.launch(Dispatchers.IO) { | ||
| val updated = | ||
| tokenRepository.updateMarketFavored( | ||
| entry.market.symbol, | ||
| entry.favoriteId, | ||
| entry.isFavored, | ||
| ) | ||
| if (updated && entry.isFavored && tokenRepository.hasAlertsByCoinId(entry.favoriteId)) { | ||
| _uiState.value = _uiState.value.copy(pendingAlertCoinId = entry.favoriteId) | ||
| } | ||
| } |
| val favoriteMarketIds by viewModel.favoriteMarketIds.collectAsStateWithLifecycle() | ||
| var isUpdatingFavorite by remember(marketId) { mutableStateOf(false) } | ||
| val isFavored = marketId in favoriteMarketIds |
| favoriteIv.setOnClickListener { | ||
| onFavoriteClick(market, isFavored) | ||
| } |
| if (change.signum() >= 0) { | ||
| MixinAppTheme.colors.marketGreen | ||
| } else { | ||
| MixinAppTheme.colors.marketRed |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (4)
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:177
- The search button has no accessibility label, so TalkBack cannot identify its action. Use the existing localized Search string as its content description.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:184
- The scan button has no accessibility label, so screen-reader users cannot distinguish it from the adjacent toolbar actions. Use the existing localized Scan string.
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:485
- This favorite control exposes no label or checked state to accessibility services, so a screen-reader user cannot tell whether activating it will add or remove the market. Provide a state-aware localized content description (for example, “Add to Watchlist” versus “Remove from Watchlist”).
contentDescription = null,
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageViewModel.kt:217
- These global flags only describe the five spot requests. They clear loading as soon as those requests finish and report an error only when all five fail, even if the selected Stock/Perpetual feed is still loading or its own request failed. On a first load this can show “No Markets” while perpetual data is in flight, or hide a Stock request failure because
allsucceeded. Track loading/error per tab or data source and derive the displayed state from the selected tab.
isLoading = false,
hasError = results.allFailed,
| val uiState: StateFlow<MarketPageUiState> = _uiState.asStateFlow() | ||
|
|
||
| private var favoriteSpotMarkets: List<MarketItem> = emptyList() | ||
| private var favoritePerpetualMarkets: List<PerpsMarket> = emptyList() |
| <androidx.constraintlayout.widget.Guideline | ||
| android:id="@+id/price_sort_guideline" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_marginTop="2dp" | ||
| android:ellipsize="end" | ||
| android:maxLines="1" | ||
| android:textColor="?attr/text_assist" | ||
| android:textSize="14sp" | ||
| app:layout_constraintBottom_toBottomOf="@id/icon_iv" | ||
| app:layout_constraintStart_toStartOf="@id/symbol_tv" | ||
| app:layout_constraintTop_toBottomOf="@id/symbol_tv" | ||
| tools:text="Vol 1.2B" /> | ||
| android:orientation="vertical" | ||
| app:layout_constraintGuide_percent="0.75" /> |
| requireContext() | ||
| .alertDialogBuilder() |
| ) { | ||
| Icon( | ||
| painter = painterResource(R.drawable.ic_close), | ||
| contentDescription = null, |
| R.drawable.ic_title_favorites | ||
| }, | ||
| ), | ||
| contentDescription = null, |
| app:layout_constraintBottom_toBottomOf="parent" | ||
| app:layout_constraintStart_toStartOf="parent" | ||
| app:layout_constraintTop_toTopOf="parent" | ||
| tools:ignore="ContentDescription" /> |
| android:drawableStart="@drawable/selector_market_favorites" | ||
| android:paddingStart="16dp" |
| android:layout_width="24dp" | ||
| android:layout_height="24dp" | ||
| android:background="?android:attr/selectableItemBackgroundBorderless" | ||
| android:padding="3dp" |
| }, | ||
| ), | ||
| ) | ||
| selectedIv.setImageResource( |
Store spot and perpetual ranks, categories, and favorites in their scoped databases. Refresh market page APIs concurrently every 30 seconds while the page is resumed.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 64 out of 65 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (6)
app/src/main/java/one/mixin/android/db/MarketCategoryDao.kt:48
- This reorders every category by market cap, so the Trending and Featured lists no longer preserve the order returned by their category endpoints.
replaceCategoryinserts relations in response order; order this query by that relation order (or persist an explicit category rank) instead.
ORDER BY CASE WHEN mr.market_cap_rank IS NULL THEN 1 ELSE 0 END,
CAST(mr.market_cap_rank AS INTEGER) ASC
app/src/main/res/layout/view_home_toolbar.xml:41
- The scan button is actionable but has no accessibility label, so screen readers cannot identify it. Replace the lint suppression with the existing Scan string as its content description.
app/src/main/res/layout/view_home_toolbar.xml:53 - The settings button is actionable but has no accessibility label, so screen readers cannot identify it. Replace the lint suppression with the existing Settings string as its content description.
app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt:34 MarketPageViewModelusesobserveAllMarkets()as the Perpetual Trending source, and the mapper intentionally preserves its input order. Sorting here by volume therefore turns Trending into a volume ranking and defeats the API-rank behavior covered byperpetualTrendingPreservesApiRankOrder. Preserve insertion/API order instead.
ORDER BY CAST(volume AS REAL) DESC, token_symbol COLLATE NOCASE ASC, market_id ASC
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:150
- Global refresh failures set
state.hasError, but the indicator branch does not pass that state toIndicatorPage. With no cached indicator, a failed GLOBAL request therefore displays “No Markets” instead of the network error used by the other tabs. PasshasErrorthrough and selectNetwork_errorfor this case.
app/src/main/res/layout/view_home_toolbar.xml:29 - The search button is actionable but has no accessibility label, so screen readers cannot identify it. Replace the lint suppression with the existing Search string as its content description.
This issue also appears in the following locations of the same file:
- line 40
- line 52
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 64 out of 65 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (9)
app/src/main/java/one/mixin/android/db/MarketCategoryDao.kt:48
- Category responses were previously consumed in API order, but this DAO now reorders every category by global market-cap rank. Since Trending is not sorted again by
MarketPageMapperand Trade also observes this flow, both surfaces lose the API's category ranking. Store each category response position on the relation and order by that rank.
ORDER BY CASE WHEN mr.market_cap_rank IS NULL THEN 1 ELSE 0 END,
CAST(mr.market_cap_rank AS INTEGER) ASC
app/src/main/res/layout/view_home_toolbar.xml:41
- This standalone scan button is exposed without an accessible name. Replace the lint suppression with the existing Scan label so screen readers can identify the action.
app/src/main/res/layout/view_home_toolbar.xml:53 - The settings button is unlabeled for accessibility services. Use the existing Settings string instead of suppressing the content-description warning.
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageModels.kt:186 - The category tabs now require exact plural, lowercase values. Cached/API markets can still use aliases such as
stock/stocksandcommodity/commodities(seePerpetualContent.kt:654-659), so those markets disappear from these tabs. Preserve the previous alias- and case-insensitive matching.
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListBottomSheetDialogFragment.kt:345 - This exact comparison regresses the alias and case-insensitive matching previously used by this sheet. Markets categorized as
stock,index,commodity, orfxwill no longer appear under their corresponding filters.
app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt:34 MarketPageViewModelfeedsobserveAllMarkets()directly to the Perpetual Trending tab, whose mapper intentionally preserves source order. Ordering this query by volume therefore replaces the API trending rank with a volume rank, contrary toperpetualTrendingPreservesApiRankOrder. Persist and query an API/category rank instead of imposing volume order here; the identicalgetAllMarketsquery needs the same treatment.
ORDER BY CAST(volume AS REAL) DESC, token_symbol COLLATE NOCASE ASC, market_id ASC
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListBottomSheetDialogFragment.kt:380
- When a periodic featured/favorite refresh removes a selected recommendation, its ID remains in
selectedRecommendationIds. The button can then stay enabled with no visible selection and repeatedly submit an already-favored or no-longer-featured market. Intersect selection with the displayed recommendations whenever the list updates.
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListBottomSheetDialogFragment.kt:280 - Favorites and featured membership are not price-tick data, but these two added requests now run every three seconds while the sheet is resumed—up to 40 extra requests per minute. Refresh them once on resume (local mutations already update their database flows) and keep only the all-markets price refresh in the polling loop.
app/src/main/res/layout/view_home_toolbar.xml:29 - This standalone search button has no accessible name, and suppressing
ContentDescriptionleaves TalkBack users with an unlabeled control. Use the existing Search string as its content description.
This issue also appears in the following locations of the same file:
- line 41
- line 53
Clearing the ImageView padding let Lottie fill the 40dp touch target, while the static icon and perpetual detail implementation used smaller visual bounds. Keep the animation at 24dp and the final icon at 22dp.
f042596 to
8795572
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 64 out of 65 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (10)
app/src/main/java/one/mixin/android/db/MarketCategoryDao.kt:48
- All category results are reordered by global market-cap rank, while
market_categoriesstores no position from the category response. This loses the API ranking/curation for Trending and Featured; the previous Trade flow consumed those response lists in order. Store a per-category position and order ranked categories by it.
ORDER BY CASE WHEN mr.market_cap_rank IS NULL THEN 1 ELSE 0 END,
CAST(mr.market_cap_rank AS INTEGER) ASC
app/src/main/res/layout/view_home_toolbar.xml:41
- This scan action is also exposed as an unlabeled ImageButton. Add the existing Scan string as its content description so assistive technology can identify it.
app/src/main/res/layout/view_home_toolbar.xml:53 - The shared third button has no accessible name, and its action differs by caller (Settings in Explore versus Market Display in MarketPage). Expose a content-description setter/attribute and set the caller-specific label; a static Settings label would be incorrect on the market screen.
app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt:24 - The new Trending path receives
observeAllMarkets()and leaves its order unchanged, but this query now orders every result by volume. Consequently the actual UI cannot preserve API rank order as the newperpetualTrendingPreservesApiRankOrdertest expects. Persist the API position (or provide a dedicated ranked query) instead of replacing it with volume order.
WHERE CAST(volume AS REAL) > 0
ORDER BY CAST(volume AS REAL) DESC, token_symbol COLLATE NOCASE ASC, market_id ASC
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageModels.kt:186
- These exact, case-sensitive comparisons drop category aliases still supported elsewhere in the codebase (
PerpetualContent.kt:654-659acceptsstock/stocksandcommodity/commodities; the previous list also acceptedindex/indicesandfx/forex). Markets using a singular alias or different case will disappear from the merged category tabs. Normalize on ingestion or retain the aliases here.
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListBottomSheetDialogFragment.kt:345 - The bottom-sheet category filter now uses an exact, case-sensitive database value, regressing the aliases previously accepted here (
stock/stocks,index/indices,commodity/commodities, andfx/forex). Those values are also still recognized byPerpetualContent.kt:654-659. Normalize categories or match the supported alias sets so valid markets remain visible.
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListBottomSheetDialogFragment.kt:280 - Favorites and Featured are account metadata/curated lists, but this loop now re-fetches both every three seconds in addition to prices—40 unnecessary requests per minute while the sheet is resumed. Local favorite mutations already update Room. Refresh these two sources once on resume (or after mutation) and keep only market prices in the ticker.
app/src/main/java/one/mixin/android/repository/PerpsMarketRepository.kt:59 - This snapshot replacement can race
updateFavorite/addFavoriteMarkets: a GET started before the user's POST can finish afterward and overwrite the newer local favorite state with its stale snapshot. The three-second refresh loop makes this likely. Serialize favorite syncs and mutations with a repository-level mutex (including the network call and DAO write).
suspend fun syncFavoriteMarkets(): List<PerpsMarket>? {
val markets = fetchMarkets(CATEGORY_FAVORITE) ?: return null
database.withTransaction {
marketDao.upsertList(markets)
favoriteDao.replaceAll(
marketIds = markets.map(PerpsMarket::marketId),
createdAt = nowInUtc(),
)
app/src/main/res/layout/view_home_toolbar.xml:29
- This interactive search button has no accessible name; suppressing the lint warning leaves TalkBack announcing an unlabeled control. Use the existing Search string as its content description.
This issue also appears in the following locations of the same file:
- line 40
- line 52
app/src/main/java/one/mixin/android/job/RefreshMarketPageJob.kt:32
- This job consists entirely of network requests but is the only comparable single-instance refresh job that does not call
requireNetwork(). Offline runs therefore execute immediately, publish failures, and are discarded instead of waiting for connectivity. Add the network constraint so JobQueue can defer the refresh until it can succeed.
Params(PRIORITY_UI_HIGH)
.singleInstanceBy(GROUP),
| successBlock = { response -> | ||
| response.data.orEmpty().map(PerpsMarket::withDefaults) | ||
| }, |
| ) | ||
| }, | ||
| successBlock = { response -> | ||
| val markets = response.data.orEmpty() |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 64 out of 65 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (10)
app/src/main/java/one/mixin/android/db/MarketCategoryDao.kt:48
- The category response order is discarded here and replaced with global market-cap order. The Trade screen consumes
trendingMarkets.take(8), so a server-ranked trending response will now show the largest-cap assets instead of its first eight results. Persist an ordinal with each category relation and order by that ordinal (with deterministic tie-breakers).
ORDER BY CASE WHEN mr.market_cap_rank IS NULL THEN 1 ELSE 0 END,
CAST(mr.market_cap_rank AS INTEGER) ASC
app/src/main/res/layout/view_home_toolbar.xml:41
- This scan action is exposed as an unlabeled button to accessibility services. Add the existing
Scanstring as its content description.
app/src/main/res/layout/view_home_toolbar.xml:53 - The settings action has no accessible label, so screen-reader users cannot identify it. Use the existing
Settingsstring instead of suppressingContentDescription.
app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt:34 - This volume sort means the Perpetual “Trending” tab cannot preserve API rank order:
MarketPageViewModelreads this flow and the mapper intentionally leaves trending entries in source order. Store and query an API ordinal (or otherwise retain the response order) instead of substituting volume order.
ORDER BY CAST(volume AS REAL) DESC, token_symbol COLLATE NOCASE ASC, market_id ASC
app/src/main/res/layout/view_home_toolbar.xml:29
- This actionable search button has no content description, so TalkBack announces an unlabeled button. Use the existing
Searchstring instead of suppressing the lint warning.
This issue also appears in the following locations of the same file:
- line 41
- line 53
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPage.kt:908
- This hard-coded user-visible label bypasses the
perps_badgeresource added in this PR. Reading it from the resource keeps the badge definition centralized and avoids diverging from resource-based UI text.
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListBottomSheetDialogFragment.kt:380 - When favorites change, the recommendation list can shrink while
selectedRecommendationIdsstill contains IDs for cards that are no longer visible. If those IDs are already favorites,addFavoriteMarketsreturns an empty set, so the callback removes nothing and leaves the Add button enabled with no visible selection. Intersect the selection with the current recommendation IDs whenever this list is rebuilt.
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageViewModel.kt:126 - Favorite mutations are not guarded per entry. Until the database flow emits, rapid taps reuse the same stale
entry.isFavoredvalue and launch duplicate add/remove requests rather than toggling back. Track in-flight favorite IDs (or optimistically update with rollback) and disable that entry’s action until the request completes, as the detail page already does.
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListAdapter.kt:70 - This click handler captures
isFavoredfrom the last bind and remains enabled while the network request is pending. A quick second tap therefore submits the same operation again instead of reversing it. Disable or debounce this market’s favorite action until the favorite-ID flow reflects completion.
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListBottomSheetDialogFragment.kt:278 - These two calls run on every 3-second quote refresh, adding about 40 favorites/featured requests per minute while the sheet is open. Those datasets do not need quote-level polling and local favorite mutations already update the database. Refresh them once on resume (or on a much slower cadence) and keep only
refreshMarkets()in this loop.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 67 out of 68 changed files in this pull request and generated no new comments.
Suppressed comments (6)
app/src/main/java/one/mixin/android/db/MarketCategoryDao.kt:48
- The category API's ranking is discarded here and replaced with global market-cap order. Both the merged market page's Trending branch and
SwapRecommendedMarketCardsconsume this flow without re-sorting (the latter takes the first eight), while the previous direct API path preserved response order. Persist the category response position inMarketCategoryRelationand order by it so Trending/Featured recommendations retain their server-defined ranking.
ORDER BY CASE WHEN mr.market_cap_rank IS NULL THEN 1 ELSE 0 END,
CAST(mr.market_cap_rank AS INTEGER) ASC
app/src/main/res/layout/view_home_toolbar.xml:41
- The scan button has no accessibility label, so assistive technologies cannot identify its action. Use the existing Scan string resource instead of suppressing the warning.
app/src/main/res/layout/view_home_toolbar.xml:53 - The settings button is unlabeled for screen readers. Replace this suppression with the existing Settings string resource.
app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt:34 - This volume sort prevents the Perpetual Trending list from preserving API rank:
MarketPageMapper.perpetualMarkets(..., TRENDING)deliberately keeps its input order (and the new test asserts that behavior), but the UI receives this DAO flow already reordered by volume. Store and query an API position/rank (or otherwise preserve response order) instead of imposing volume order for the all-markets flow.
ORDER BY CAST(volume AS REAL) DESC, token_symbol COLLATE NOCASE ASC, market_id ASC
app/src/main/res/layout/view_home_toolbar.xml:29
- The search button has no accessibility label, so screen readers announce an unlabeled control. Replace the lint suppression with the existing Search string resource.
This issue also appears in the following locations of the same file:
- line 41
- line 53
app/src/main/java/one/mixin/android/ui/wallet/MarketDetailsFragment.kt:129
- The favorite control remains enabled while this asynchronous request runs, and line 141 immediately flips
marketItem.isFavored. A quick second tap can therefore enqueue the inverse operation; if the first removal completes later, its callback can offer to delete alerts even though the market has already been re-added. Disable/serialize the control until the request completes and only show the destructive prompt when the latest state is still removed.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 69 out of 70 changed files in this pull request and generated no new comments.
Suppressed comments (4)
app/src/main/java/one/mixin/android/db/MarketCategoryDao.kt:48
- This ordering discards the order returned by ranked category endpoints.
replaceCategoryinserts relations in response order, but trending/featured consumers now receive market-cap order; the previousTradeFragmentpath used the API list directly, and the equivalent perps DAO deliberately orders by relationrowid. Order bymc.rowidso API-ranked categories remain ranked as returned.
ORDER BY CASE WHEN mr.market_cap_rank IS NULL THEN 1 ELSE 0 END,
CAST(mr.market_cap_rank AS INTEGER) ASC
app/src/main/java/one/mixin/android/job/RefreshMarketPageJob.kt:32
- This replacement refresh job performs only network work, but unlike the removed refresh jobs it has no network constraint. When launched offline, JobQueue executes all requests immediately, records every source as failed, and removes the job instead of waiting for connectivity. Preserve the previous retry-on-connect behavior by requiring a network here (as other refresh jobs such as
RefreshSnapshotsJobdo).
Params(PRIORITY_UI_HIGH)
.singleInstanceBy(GROUP),
app/src/main/java/one/mixin/android/ui/home/web3/market/MarketPageModels.kt:199
- These exact plural comparisons regress the category aliases supported by the existing perps UI. Migration 6→7 preserves cached markets, so cached values such as
index,commodity, orfx(and differently cased values) disappear from the merged page until replaced. Keep the prior singular/plural, case-insensitive matching when filtering.
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListBottomSheetDialogFragment.kt:345 - The bottom sheet previously accepted singular/plural aliases and ignored case, but this exact comparison now hides preserved cached categories such as
stock,index,commodity, andfx.PerpetualContent.kt:654-659still treats these aliases as equivalent. Normalize category values or restore alias sets before filtering.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 68 out of 69 changed files in this pull request and generated no new comments.
Suppressed comments (5)
app/src/main/java/one/mixin/android/db/MarketCategoryDao.kt:48
- This orders every ranked category by the global market-cap rank rather than by the category API's rank.
TradeFragmentnow consumes this flow and the recommendation cards calltake(8), so Trending/Top Gainers/Top Losers can show the largest-cap assets instead of the category leaders. Preserve the relation insertion/API order, asPerpsMarketCategoryDaodoes.
ORDER BY CASE WHEN mr.market_cap_rank IS NULL THEN 1 ELSE 0 END,
CAST(mr.market_cap_rank AS INTEGER) ASC
app/src/main/res/layout/view_home_toolbar.xml:41
- The scan action has no accessible name, so screen-reader users only encounter an unlabeled button. Use the existing localized Scan QR string instead of suppressing the lint warning.
app/src/main/res/layout/view_home_toolbar.xml:53 - The settings/display action has no accessible name, so screen-reader users only encounter an unlabeled button. Use the existing localized Settings string instead of suppressing the lint warning.
app/src/main/res/layout/view_home_toolbar.xml:29 - The search action has no accessible name, so screen-reader users only encounter an unlabeled button. Use the existing localized Search string instead of suppressing the lint warning.
This issue also appears in the following locations of the same file:
- line 40
- line 52
app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListBottomSheetDialogFragment.kt:281
- These favorite and featured syncs are inside the 3-second price polling loop, adding up to 40 extra network requests per minute while this sheet is open. Refresh these membership datasets once when the lifecycle collection starts (or on a substantially slower cadence), and keep only market prices in the fast loop.
No description provided.