Skip to content

Commit

Permalink
Implement Etf page
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelekol committed May 28, 2024
1 parent ab8ff3c commit 551e355
Show file tree
Hide file tree
Showing 19 changed files with 466 additions and 56 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ dependencies {
implementation 'com.github.horizontalsystems:ethereum-kit-android:3d83900'
implementation 'com.github.horizontalsystems:blockchain-fee-rate-kit-android:1d3bd49'
implementation 'com.github.horizontalsystems:binance-chain-kit-android:c1509a2'
implementation 'com.github.horizontalsystems:market-kit-android:103bf39'
implementation 'com.github.horizontalsystems:market-kit-android:b9abcef'
implementation 'com.github.horizontalsystems:solana-kit-android:ec238b4'
implementation 'com.github.horizontalsystems:tron-kit-android:dc3dca7'
// Zcash SDK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,10 @@ class MarketKitWrapper(
return marketKit.sendStats(stats, appVersion, appId)
}

// Etf

fun etfs(currencyCode: String) = marketKit.etfSingle(currencyCode)

fun etfPoints(currencyCode: String) = marketKit.etfPointSingle(currencyCode)

}
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,8 @@ val AccountType.statAccountType: String
val MetricsType.statPage: StatPage
get() = when (this) {
MetricsType.TotalMarketCap -> StatPage.GlobalMetricsMarketCap
MetricsType.BtcDominance -> StatPage.GlobalMetricsMarketCap
MetricsType.Volume24h -> StatPage.GlobalMetricsVolume
MetricsType.DefiCap -> StatPage.GlobalMetricsDefiCap
MetricsType.Etf -> StatPage.GlobalMetricsEtf
MetricsType.TvlInDefi -> StatPage.GlobalMetricsTvlInDefi
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ enum class StatPage(val key: String) {
Faq("faq"),
GlobalMetricsMarketCap("global_metrics_market_cap"),
GlobalMetricsVolume("global_metrics_volume"),
GlobalMetricsDefiCap("global_metrics_defi_cap"),
GlobalMetricsEtf("global_metrics_etf"),
GlobalMetricsTvlInDefi("global_metrics_tvl_in_defi"),
Guide("guide"),
ImportFull("import_full"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ fun MetricsBoard(
private fun openMetricsPage(metricsType: MetricsType, navController: NavController) {
if (metricsType == MetricsType.TvlInDefi) {
navController.slideFromBottom(R.id.tvlFragment)
} else if (metricsType == MetricsType.Etf) {
navController.slideFromBottom(R.id.etfFragment)
} else {
navController.slideFromBottom(R.id.metricsPageFragment, metricsType)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class MarketViewModel(
var volume24hDiff: BigDecimal? = null
var tvl: BigDecimal? = null
var tvlDiff: BigDecimal? = null
var btcDominance: BigDecimal? = null
var btcDominanceDiff: BigDecimal? = null

if (globalMarketPoints.isNotEmpty()) {
val startingPoint = globalMarketPoints.first()
Expand All @@ -97,6 +99,9 @@ class MarketViewModel(
volume24h = endingPoint.volume24h
volume24hDiff = diff(startingPoint.volume24h, volume24h)

btcDominance = endingPoint.btcDominance
btcDominanceDiff = diff(startingPoint.btcDominance, btcDominance)

tvl = endingPoint.tvl
tvlDiff = diff(startingPoint.tvl, tvl)
}
Expand All @@ -116,12 +121,21 @@ class MarketViewModel(
volume24hDiff?.let { it > BigDecimal.ZERO } ?: false,
MetricsType.Volume24h
),
MarketOverviewViewItem(
"BTC Dominance",
btcDominance?.let {
App.numberFormatter.format(it, 0, 2, suffix = "%")
} ?: "-",
btcDominanceDiff?.let { getDiff(it) } ?: "----",
btcDominanceDiff?.let { it > BigDecimal.ZERO } ?: false,
MetricsType.TotalMarketCap
),
MarketOverviewViewItem(
"ETF",
defiMarketCap?.let { formatFiatShortened(it, baseCurrency.symbol) } ?: "-",
defiMarketCapDiff?.let { getDiff(it) } ?: "----",
defiMarketCapDiff?.let { it > BigDecimal.ZERO } ?: false,
MetricsType.DefiCap
MetricsType.Etf
),
MarketOverviewViewItem(
"TVL",
Expand All @@ -137,7 +151,7 @@ class MarketViewModel(

private fun getDiff(it: BigDecimal): String {
val sign = if (it >= BigDecimal.ZERO) "+" else "-"
return "${App.numberFormatter.format(it.abs(), 0, 2, sign, "%")}%"
return App.numberFormatter.format(it.abs(), 0, 2, sign, "%")
}

private fun formatFiatShortened(value: BigDecimal, symbol: String): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package io.horizontalsystems.bankwallet.modules.market.etf

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.fragment.app.viewModels
import androidx.navigation.NavController
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.BaseComposeFragment
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.ui.compose.ComposeAppTheme
import io.horizontalsystems.bankwallet.ui.compose.HSSwipeRefresh
import io.horizontalsystems.bankwallet.ui.compose.Select
import io.horizontalsystems.bankwallet.ui.compose.TranslatableString
import io.horizontalsystems.bankwallet.ui.compose.components.AlertGroup
import io.horizontalsystems.bankwallet.ui.compose.components.AppBar
import io.horizontalsystems.bankwallet.ui.compose.components.ButtonSecondaryWithIcon
import io.horizontalsystems.bankwallet.ui.compose.components.DescriptionCard
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.MarketCoinClear
import io.horizontalsystems.bankwallet.ui.compose.components.MenuItem

class EtfFragment : BaseComposeFragment() {

@Composable
override fun GetContent(navController: NavController) {
val factory = EtfModule.Factory()
val viewModel by viewModels<EtfViewModel> { factory }
EtfPage(viewModel, navController)
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun EtfPage(
viewModel: EtfViewModel,
navController: NavController,
) {
val uiState = viewModel.uiState
val title = stringResource(id = R.string.MarketEtf_Title)
val description = stringResource(id = R.string.MarketEtf_Description)
var openPeriodSelector by rememberSaveable { mutableStateOf(false) }
var openSortingSelector by rememberSaveable { mutableStateOf(false) }

Column(Modifier.background(color = ComposeAppTheme.colors.tyler)) {
AppBar(
menuItems = listOf(
MenuItem(
title = TranslatableString.ResString(R.string.Button_Close),
icon = R.drawable.ic_close,
onClick = {
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 -> {
val listState = rememberSaveable(
uiState.sortBy,
saver = LazyListState.Saver
) {
LazyListState()
}

LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
contentPadding = PaddingValues(bottom = 32.dp),
) {
item {
DescriptionCard(
title,
description,
ImageSource.Remote("https://cdn.blocksdecoded.com/category-icons/[email protected]")
)
}
//todo Add chart
// item {
// Chart(chartViewModel = chartViewModel)
// }
stickyHeader {
HeaderSorting(borderBottom = true, borderTop = true) {
HSpacer(width = 16.dp)
ButtonSecondaryWithIcon(
modifier = Modifier.height(28.dp),
onClick = {
openSortingSelector = true
},
title = stringResource(uiState.sortBy.titleResId),
iconRight = painterResource(R.drawable.ic_down_arrow_20),
)
HSpacer(width = 8.dp)
ButtonSecondaryWithIcon(
modifier = Modifier.height(28.dp),
onClick = {
openPeriodSelector = true
},
title = stringResource(uiState.timeDuration.titleResId),
iconRight = painterResource(R.drawable.ic_down_arrow_20),
)
HSpacer(width = 16.dp)
}
}
items(uiState.viewItems) { viewItem ->
MarketCoinClear(
subtitle = viewItem.subtitle,
title = viewItem.title,
coinIconUrl = viewItem.iconUrl,
coinIconPlaceholder = R.drawable.ic_platform_placeholder_24,
value = viewItem.value,
marketDataValue = viewItem.subvalue,
label = viewItem.rank,
)
}
}
}
}
}
}
}
if (openPeriodSelector) {
AlertGroup(
title = R.string.CoinPage_Period,
select = Select(uiState.timeDuration, viewModel.timeDurations),
onSelect = { selected ->
viewModel.onSelectTimeDuration(selected)
openPeriodSelector = false
},
onDismiss = {
openPeriodSelector = false
}
)
}
if (openSortingSelector) {
AlertGroup(
title = R.string.Market_Sort_PopupTitle,
select = Select(uiState.sortBy, viewModel.sortByOptions),
onSelect = { selected ->
viewModel.onSelectSortBy(selected)
openSortingSelector = false
},
onDismiss = {
openSortingSelector = false
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.horizontalsystems.bankwallet.modules.market.etf

import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.ViewState
import io.horizontalsystems.bankwallet.modules.market.MarketDataValue
import io.horizontalsystems.bankwallet.modules.market.TimeDuration
import io.horizontalsystems.bankwallet.ui.compose.TranslatableString
import io.horizontalsystems.bankwallet.ui.compose.WithTranslatableTitle

object EtfModule {

class Factory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return EtfViewModel(App.currencyManager, App.marketKit) as T
}
}

@Immutable
data class EtfViewItem(
val title: String,
val iconUrl: String,
val subtitle: String,
val value: String?,
val subvalue: MarketDataValue?,
val rank: String?,
)

@Immutable
data class UiState(
val viewItems: List<EtfViewItem>,
val viewState: ViewState,
val isRefreshing: Boolean,
val timeDuration: TimeDuration,
val sortBy: SortBy,
)

enum class SortBy(@StringRes val titleResId: Int): WithTranslatableTitle {
HighestAssets(R.string.MarketEtf_HighestAssets),
LowestAssets(R.string.MarketEtf_LowestAssets),
Inflow(R.string.MarketEtf_Inflow),
Outflow(R.string.MarketEtf_Outflow);

override val title = TranslatableString.ResString(titleResId)
}
}

Loading

0 comments on commit 551e355

Please sign in to comment.