Skip to content

Feat/merge market pages#6517

Draft
SeniorZhai wants to merge 5 commits into
masterfrom
feat/merge-market-pages
Draft

Feat/merge market pages#6517
SeniorZhai wants to merge 5 commits into
masterfrom
feat/merge-market-pages

Conversation

@SeniorZhai

Copy link
Copy Markdown
Member

No description provided.

Copilot AI review requested due to automatic review settings July 23, 2026 07:10

Copilot AI left a comment

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.

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.

Comment on lines +115 to +117
if (oldSettings.priceChangePeriod != settings.priceChangePeriod) {
refreshMarkets()
}
Comment on lines +108 to +111
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,
Copilot AI review requested due to automatic review settings July 23, 2026 07:15

Copilot AI left a comment

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.

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()
            }

Comment on lines +173 to +176
_uiState.value =
_uiState.value.copy(
isLoading = false,
hasError = results.allFailed,
Comment on lines +132 to +134
if (period == MarketPriceChangePeriod.SEVEN_DAYS) {
return markets
}
Comment on lines +152 to +155
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,
Copilot AI review requested due to automatic review settings July 23, 2026 07:26

Copilot AI left a comment

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.

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() calls refreshMarkets(), 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

  • allFailed masks failures for the category the user is viewing whenever any unrelated request succeeds. For example, if trending fails but all succeeds, 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 reported MORE_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 Search string 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 Scan string 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 Close string can be used directly.
                                contentDescription = null,

R.drawable.ic_asset_favorites
},
),
contentDescription = null,
}
Spacer(modifier = Modifier.width(4.dp))
SortLabel(
text = "Vol",
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.

2 participants