From 2c8a27141db5649c8f28b04a3919ced9de692104 Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 22 May 2024 15:59:30 +0600 Subject: [PATCH] Implement updated Top pairs tab in Market --- .../bankwallet/core/Extensions.kt | 3 + .../bankwallet/modules/market/MarketScreen.kt | 4 +- .../market/overview/MarketOverviewModule.kt | 10 +- .../market/overview/MarketOverviewScreen.kt | 2 +- .../market/overview/ui/TopPairsBoardView.kt | 38 +--- .../market/toppairs/TopPairsFragment.kt | 100 ----------- .../modules/market/toppairs/TopPairsScreen.kt | 169 ++++++++++++++++++ .../market/toppairs/TopPairsViewModel.kt | 25 ++- .../topplatforms/TopPlatformsViewModel.kt | 2 +- app/src/main/res/navigation/main_graph.xml | 3 - app/src/main/res/values/strings.xml | 3 +- .../main/res/drawable/ic_arrow_down_20.xml | 2 +- 12 files changed, 209 insertions(+), 152 deletions(-) delete mode 100644 app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsFragment.kt create mode 100644 app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsScreen.kt diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/Extensions.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/Extensions.kt index d51e5b18fe6..07c78ebb85d 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/Extensions.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/Extensions.kt @@ -35,6 +35,9 @@ val Optional.orNull: T? val Platform.iconUrl: String get() = "https://cdn.blocksdecoded.com/blockchain-icons/32px/$uid@3x.png" +val String.coinIconUrl: String + get() = "https://cdn.blocksdecoded.com/coin-icons/32px/$this@3x.png" + val CoinCategory.imageUrl: String get() = "https://cdn.blocksdecoded.com/category-icons/$uid@3x.png" diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/MarketScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/MarketScreen.kt index 895b694525e..0ea53aa231d 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/MarketScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/MarketScreen.kt @@ -34,6 +34,7 @@ import io.horizontalsystems.bankwallet.modules.market.MarketModule.Tab import io.horizontalsystems.bankwallet.modules.market.favorites.MarketFavoritesScreen import io.horizontalsystems.bankwallet.modules.market.posts.MarketPostsScreen import io.horizontalsystems.bankwallet.modules.market.topcoins.TopCoins +import io.horizontalsystems.bankwallet.modules.market.toppairs.TopPairsScreen import io.horizontalsystems.bankwallet.modules.market.topplatforms.TopPlatforms import io.horizontalsystems.bankwallet.modules.metricchart.MetricsType import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme @@ -143,8 +144,7 @@ fun TabsSection( TopPlatforms(navController) stat(page = StatPage.MarketOverview, event = StatEvent.Open(StatPage.TopPlatforms)) } - Tab.Pairs -> { - } + Tab.Pairs -> TopPairsScreen() Tab.Sectors -> { } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewModule.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewModule.kt index 17b0f5ca396..3b3d58babd5 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewModule.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewModule.kt @@ -103,9 +103,12 @@ data class TopPairViewItem( val title: String, val rank: String, val name: String, + val baseCoinUid: String?, + val targetCoinUid: String?, val iconUrl: String?, val tradeUrl: String?, - val volume: String, + val volume: BigDecimal, + val volumeInFiat: String, val price: String? ) { companion object { @@ -123,10 +126,13 @@ data class TopPairViewItem( return TopPairViewItem( title = "${topPair.base}/${topPair.target}", name = topPair.marketName ?: "", + baseCoinUid = topPair.baseCoinUid, + targetCoinUid = topPair.targetCoinUid, iconUrl = topPair.marketLogo, rank = topPair.rank.toString(), tradeUrl = topPair.tradeUrl, - volume = volumeStr, + volume = topPair.volume, + volumeInFiat = volumeStr, price = priceStr, ) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewScreen.kt index facee116ba3..ed322e336bf 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewScreen.kt @@ -108,7 +108,7 @@ fun MarketOverviewScreen( } } ) { - navController.slideFromBottom(R.id.topPairsFragment) + //navController.slideFromBottom(R.id.topPairsFragment) stat(page = StatPage.MarketOverview, event = StatEvent.Open(StatPage.TopMarketPairs)) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/ui/TopPairsBoardView.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/ui/TopPairsBoardView.kt index 86f6e72b0b3..976315a88ef 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/ui/TopPairsBoardView.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/ui/TopPairsBoardView.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -14,13 +13,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import io.horizontalsystems.bankwallet.R -import io.horizontalsystems.bankwallet.modules.market.MarketDataValue import io.horizontalsystems.bankwallet.modules.market.overview.TopPairViewItem +import io.horizontalsystems.bankwallet.modules.market.toppairs.TopPairItem import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme -import io.horizontalsystems.bankwallet.ui.compose.components.CoinImage -import io.horizontalsystems.bankwallet.ui.compose.components.MarketCoinFirstRow -import io.horizontalsystems.bankwallet.ui.compose.components.MarketCoinSecondRow -import io.horizontalsystems.bankwallet.ui.compose.components.SectionItemBorderedRowUniversalClear @Composable fun TopPairsBoardView( @@ -51,34 +46,3 @@ fun TopPairsBoardView( Spacer(modifier = Modifier.height(24.dp)) } -@Composable -fun TopPairItem( - item: TopPairViewItem, - borderTop: Boolean = false, - borderBottom: Boolean = false, - onItemClick: (TopPairViewItem) -> Unit, -) { - SectionItemBorderedRowUniversalClear( - borderTop = borderTop, - borderBottom = borderBottom, - onClick = { onItemClick(item) } - ) { - CoinImage( - iconUrl = item.iconUrl, - placeholder = R.drawable.ic_platform_placeholder_32, - modifier = Modifier - .padding(end = 16.dp) - .size(32.dp) - .clip(RoundedCornerShape(8.dp)) - ) - Column(modifier = Modifier.fillMaxWidth()) { - MarketCoinFirstRow(item.title, item.volume) - Spacer(modifier = Modifier.height(3.dp)) - MarketCoinSecondRow( - subtitle = item.name, - marketDataValue = item.price?.let { MarketDataValue.Volume(it) }, - label = item.rank - ) - } - } -} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsFragment.kt deleted file mode 100644 index a2c4f4e288d..00000000000 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsFragment.kt +++ /dev/null @@ -1,100 +0,0 @@ -package io.horizontalsystems.bankwallet.modules.market.toppairs - -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController -import io.horizontalsystems.bankwallet.R -import io.horizontalsystems.bankwallet.core.BaseComposeFragment -import io.horizontalsystems.bankwallet.core.stats.StatEvent -import io.horizontalsystems.bankwallet.core.stats.StatPage -import io.horizontalsystems.bankwallet.core.stats.stat -import io.horizontalsystems.bankwallet.entities.ViewState -import io.horizontalsystems.bankwallet.modules.coin.overview.ui.Loading -import io.horizontalsystems.bankwallet.modules.market.ImageSource -import io.horizontalsystems.bankwallet.modules.market.overview.ui.TopPairItem -import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme -import io.horizontalsystems.bankwallet.ui.compose.HSSwipeRefresh -import io.horizontalsystems.bankwallet.ui.compose.components.DescriptionCard -import io.horizontalsystems.bankwallet.ui.compose.components.ListErrorView -import io.horizontalsystems.bankwallet.ui.compose.components.TopCloseButton -import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer -import io.horizontalsystems.bankwallet.ui.helpers.LinkHelper - -class TopPairsFragment : BaseComposeFragment() { - @Composable - override fun GetContent(navController: NavController) { - TopPairsScreen(navController) - } -} - -@Composable -fun TopPairsScreen(navController: NavController) { - val viewModel = viewModel(factory = TopPairsViewModel.Factory()) - val uiState = viewModel.uiState - val context = LocalContext.current - - Scaffold( - backgroundColor = ComposeAppTheme.colors.tyler, - ) { - Column(modifier = Modifier.padding(it)) { - TopCloseButton { navController.popBackStack() } - - HSSwipeRefresh( - refreshing = uiState.isRefreshing, - onRefresh = viewModel::refresh - ) { - Crossfade(uiState.viewState, label = "") { viewState -> - when (viewState) { - ViewState.Loading -> { - Loading() - } - - is ViewState.Error -> { - ListErrorView( - stringResource(R.string.SyncError), - viewModel::onErrorClick - ) - } - - ViewState.Success -> { - LazyColumn( - modifier = Modifier.fillMaxSize() - ) { - item { - DescriptionCard( - stringResource(id = R.string.TopPairs_Title), - stringResource(id = R.string.TopPairs_Description), - ImageSource.Local(R.drawable.ic_token_pairs) - ) - } - itemsIndexed(uiState.items) { i, item -> - TopPairItem(item, borderTop = i == 0, borderBottom = true) { - it.tradeUrl?.let { - LinkHelper.openLinkInAppBrowser(context, it) - - stat(page = StatPage.TopMarketPairs, event = StatEvent.Open(StatPage.ExternalMarketPair)) - } - } - } - item { - VSpacer(height = 32.dp) - } - } - } - } - } - } - } - } -} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsScreen.kt new file mode 100644 index 00000000000..4698e945d91 --- /dev/null +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsScreen.kt @@ -0,0 +1,169 @@ +package io.horizontalsystems.bankwallet.modules.market.toppairs + +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import io.horizontalsystems.bankwallet.R +import io.horizontalsystems.bankwallet.core.coinIconUrl +import io.horizontalsystems.bankwallet.core.stats.StatEvent +import io.horizontalsystems.bankwallet.core.stats.StatPage +import io.horizontalsystems.bankwallet.core.stats.stat +import io.horizontalsystems.bankwallet.entities.ViewState +import io.horizontalsystems.bankwallet.modules.coin.overview.ui.Loading +import io.horizontalsystems.bankwallet.modules.market.MarketDataValue +import io.horizontalsystems.bankwallet.modules.market.overview.TopPairViewItem +import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme +import io.horizontalsystems.bankwallet.ui.compose.HSSwipeRefresh +import io.horizontalsystems.bankwallet.ui.compose.components.ButtonSecondaryWithIcon +import io.horizontalsystems.bankwallet.ui.compose.components.CoinImage +import io.horizontalsystems.bankwallet.ui.compose.components.HSpacer +import io.horizontalsystems.bankwallet.ui.compose.components.HeaderSorting +import io.horizontalsystems.bankwallet.ui.compose.components.ListErrorView +import io.horizontalsystems.bankwallet.ui.compose.components.MarketCoinFirstRow +import io.horizontalsystems.bankwallet.ui.compose.components.MarketCoinSecondRow +import io.horizontalsystems.bankwallet.ui.compose.components.SectionItemBorderedRowUniversalClear +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer +import io.horizontalsystems.bankwallet.ui.helpers.LinkHelper + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun TopPairsScreen() { + val viewModel = viewModel(factory = TopPairsViewModel.Factory()) + val uiState = viewModel.uiState + val context = LocalContext.current + + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + ) { + Column(modifier = Modifier.padding(it)) { + HSSwipeRefresh( + refreshing = uiState.isRefreshing, + onRefresh = viewModel::refresh + ) { + Crossfade(uiState.viewState, label = "") { viewState -> + when (viewState) { + ViewState.Loading -> { + Loading() + } + + is ViewState.Error -> { + ListErrorView( + stringResource(R.string.SyncError), + viewModel::onErrorClick + ) + } + + ViewState.Success -> { + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + stickyHeader { + HeaderSorting(borderBottom = true) { + HSpacer(width = 16.dp) + ButtonSecondaryWithIcon( + modifier = Modifier.height(28.dp), + onClick = { + viewModel.toggleSorting() + }, + title = stringResource(R.string.Market_Volume), + iconRight = painterResource( + if (uiState.sortDescending) R.drawable.ic_arrow_down_20 else R.drawable.ic_arrow_up_20 + ), + ) + HSpacer(width = 16.dp) + } + } + itemsIndexed(uiState.items) { i, item -> + TopPairItem(item, borderTop = i == 0, borderBottom = true) { + it.tradeUrl?.let { + LinkHelper.openLinkInAppBrowser(context, it) + + stat( + page = StatPage.TopMarketPairs, + event = StatEvent.Open(StatPage.ExternalMarketPair) + ) + } + } + } + item { + VSpacer(height = 32.dp) + } + } + } + } + } + } + } + } +} + +@Composable +fun TopPairItem( + item: TopPairViewItem, + borderTop: Boolean = false, + borderBottom: Boolean = false, + onItemClick: (TopPairViewItem) -> Unit, +) { + SectionItemBorderedRowUniversalClear( + borderTop = borderTop, + borderBottom = borderBottom, + onClick = { onItemClick(item) } + ) { + Box( + modifier = Modifier + .padding(end = 16.dp) + .width(54.dp) + ) { + CoinImage( + iconUrl = item.targetCoinUid?.coinIconUrl, + placeholder = R.drawable.ic_platform_placeholder_32, + modifier = Modifier + .size(32.dp) + .background(ComposeAppTheme.colors.tyler) + .clip(CircleShape) + .align(Alignment.TopEnd) + ) + CoinImage( + iconUrl = item.baseCoinUid?.coinIconUrl, + placeholder = R.drawable.ic_platform_placeholder_32, + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(ComposeAppTheme.colors.tyler) + .align(Alignment.TopStart) + ) + } + + Column(modifier = Modifier.fillMaxWidth()) { + MarketCoinFirstRow(item.title, item.volumeInFiat) + Spacer(modifier = Modifier.height(3.dp)) + MarketCoinSecondRow( + subtitle = item.name, + marketDataValue = item.price?.let { MarketDataValue.Volume(it) }, + label = item.rank + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsViewModel.kt index f1cb2d81034..e536a4f06fe 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/toppairs/TopPairsViewModel.kt @@ -23,6 +23,7 @@ class TopPairsViewModel( private var isRefreshing = false private var items = listOf() private var viewState: ViewState = ViewState.Loading + private var sortDescending = true init { viewModelScope.launch { @@ -41,15 +42,21 @@ class TopPairsViewModel( override fun createState() = TopPairsUiState( isRefreshing = isRefreshing, items = items, - viewState = viewState + viewState = viewState, + sortDescending = sortDescending, ) + private fun sortItems(items: List) = + if (sortDescending) items.sortedByDescending { it.volume } else items.sortedBy { it.volume } + private suspend fun fetchItems() = withContext(Dispatchers.Default) { try { - val topPairs = marketKit.topPairsSingle(currencyManager.baseCurrency.code, 1, 100).await() - items = topPairs.map { + val topPairs = + marketKit.topPairsSingle(currencyManager.baseCurrency.code, 1, 100).await() + val pairs = topPairs.map { TopPairViewItem.createFromTopPair(it, currencyManager.baseCurrency.symbol) } + items = sortItems(pairs) viewState = ViewState.Success } catch (e: Throwable) { viewState = ViewState.Error(e) @@ -72,6 +79,15 @@ class TopPairsViewModel( refresh() } + fun toggleSorting() { + sortDescending = !sortDescending + emitState() + viewModelScope.launch { + items = sortItems(items) + emitState() + } + } + class Factory : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -84,5 +100,6 @@ class TopPairsViewModel( data class TopPairsUiState( val isRefreshing: Boolean, val items: List, - val viewState: ViewState + val viewState: ViewState, + val sortDescending: Boolean, ) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/topplatforms/TopPlatformsViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/topplatforms/TopPlatformsViewModel.kt index 8cf4a02ff98..d345be1af19 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/topplatforms/TopPlatformsViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/topplatforms/TopPlatformsViewModel.kt @@ -30,7 +30,7 @@ class TopPlatformsViewModel( TimeDuration.ThreeMonths, ) - private var sortingField = SortingField.HighestCap + private var sortingField = SortingField.TopGainers private var timePeriod = timeDuration ?: TimeDuration.OneDay diff --git a/app/src/main/res/navigation/main_graph.xml b/app/src/main/res/navigation/main_graph.xml index c6f554ca24b..3ed61736d35 100644 --- a/app/src/main/res/navigation/main_graph.xml +++ b/app/src/main/res/navigation/main_graph.xml @@ -216,9 +216,6 @@ - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 39e07ed798c..9bf500a67b7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -219,7 +219,7 @@ Overview News Coins - Platform + Platforms Pairs Sectors Watchlist @@ -227,6 +227,7 @@ Top Coins Top Coins by market cap rank Signals + Volume Top 100 Top 200 diff --git a/components/icons/src/main/res/drawable/ic_arrow_down_20.xml b/components/icons/src/main/res/drawable/ic_arrow_down_20.xml index 0b6171185e7..be6b5fb971d 100644 --- a/components/icons/src/main/res/drawable/ic_arrow_down_20.xml +++ b/components/icons/src/main/res/drawable/ic_arrow_down_20.xml @@ -5,6 +5,6 @@ android:viewportHeight="20">