Skip to content

Commit df35d89

Browse files
committed
feat(token/info): load and display real market cap data
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 28f7c63 commit df35d89

20 files changed

Lines changed: 528 additions & 231 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.flipcash.app.core.data
2+
3+
import kotlin.contracts.ExperimentalContracts
4+
import kotlin.contracts.contract
5+
6+
sealed class Loadable<T> {
7+
data class Loading<T>(internal val data: T? = null) : Loadable<T>()
8+
data class Loaded<T>(val data: T) : Loadable<T>()
9+
data class Error<T>(
10+
val message: String?,
11+
val error: Throwable? = null,
12+
val retry: (() -> Unit)? = null,
13+
) : Loadable<T>()
14+
15+
val dataOrNull: T?
16+
get() = when (this) {
17+
is Loading -> data
18+
is Loaded -> data
19+
is Error -> null
20+
}
21+
}
22+
23+
@OptIn(ExperimentalContracts::class)
24+
inline fun <reified T> Loadable<T>.isLoading(): Boolean {
25+
contract { returns(true) implies (this@isLoading is Loadable.Loading<T>) }
26+
return this is Loadable.Loading<T>
27+
}
28+
29+
@OptIn(ExperimentalContracts::class)
30+
inline fun <reified T> Loadable<T>.isError(): Boolean {
31+
contract { returns(true) implies (this@isError is Loadable.Error<T>) }
32+
return this is Loadable.Error<T>
33+
}
34+
35+
@OptIn(ExperimentalContracts::class)
36+
inline fun <reified T> Loadable<T>.isLoaded(): Boolean {
37+
contract { returns(true) implies (this@isLoaded is Loadable.Loaded<T>) }
38+
return this is Loadable.Loaded<T>
39+
}

apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenInfoScreen.kt

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
package com.flipcash.app.tokens.internal
22

3-
import androidx.compose.foundation.background
43
import androidx.compose.foundation.layout.Arrangement
54
import androidx.compose.foundation.layout.Box
65
import androidx.compose.foundation.layout.PaddingValues
76
import androidx.compose.foundation.layout.Row
87
import androidx.compose.foundation.layout.Spacer
9-
import androidx.compose.foundation.layout.WindowInsets
108
import androidx.compose.foundation.layout.fillMaxSize
119
import androidx.compose.foundation.layout.fillMaxWidth
1210
import androidx.compose.foundation.layout.height
13-
import androidx.compose.foundation.layout.navigationBars
1411
import androidx.compose.foundation.layout.navigationBarsPadding
15-
import androidx.compose.foundation.layout.offset
1612
import androidx.compose.foundation.layout.padding
17-
import androidx.compose.foundation.layout.wrapContentSize
1813
import androidx.compose.foundation.lazy.LazyColumn
1914
import androidx.compose.foundation.lazy.rememberLazyListState
2015
import androidx.compose.material.Divider
@@ -25,51 +20,31 @@ import androidx.compose.runtime.getValue
2520
import androidx.compose.runtime.mutableStateOf
2621
import androidx.compose.runtime.remember
2722
import androidx.compose.runtime.setValue
28-
import androidx.compose.runtime.snapshotFlow
2923
import androidx.compose.ui.Alignment
3024
import androidx.compose.ui.Modifier
31-
import androidx.compose.ui.graphics.Brush
32-
import androidx.compose.ui.graphics.Color
33-
import androidx.compose.ui.input.nestedscroll.nestedScroll
34-
import androidx.compose.ui.platform.LocalDensity
35-
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
3625
import androidx.compose.ui.res.stringResource
3726
import androidx.compose.ui.unit.dp
3827
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3928
import com.flipcash.app.core.AppRoute
29+
import com.flipcash.app.core.data.Loadable
4030
import com.flipcash.app.core.money.RegionSelectionKind
4131
import com.flipcash.app.core.tokens.TokenSwapPurpose
4232
import com.flipcash.app.tokens.TokenInfoViewModel
43-
import com.flipcash.app.tokens.data.MarketTrend
4433
import com.flipcash.app.tokens.internal.components.info.MarketCapSection
4534
import com.flipcash.app.tokens.internal.components.info.TokenBalance
4635
import com.flipcash.app.tokens.internal.components.info.TokenDetailsSection
4736
import com.flipcash.app.tokens.internal.components.marketcap.generateMarketCapData
4837
import com.flipcash.features.tokens.R
4938
import com.getcode.theme.CodeTheme
50-
import com.getcode.ui.core.addIf
51-
import com.getcode.ui.core.debugBounds
5239
import com.getcode.ui.core.drawWithGradient
53-
import com.getcode.ui.core.isScrolledToStart
5440
import com.getcode.ui.core.measured
5541
import com.getcode.ui.core.verticalScrollStateGradient
5642
import com.getcode.ui.theme.ButtonState
5743
import com.getcode.ui.theme.CodeButton
5844
import com.getcode.ui.theme.CodeScaffold
59-
import com.getcode.ui.utils.DisableSheetGestures
60-
import com.getcode.ui.utils.DisableSheetGesturesWhileScrolling
61-
import com.getcode.ui.utils.LocalSheetGesturesState
6245
import com.getcode.ui.utils.calculateEndPadding
6346
import com.getcode.ui.utils.calculateStartPadding
6447
import com.getcode.ui.utils.sheetResignmentBehavior
65-
import dev.chrisbanes.haze.HazeProgressive
66-
import dev.chrisbanes.haze.HazeState
67-
import dev.chrisbanes.haze.hazeEffect
68-
import dev.chrisbanes.haze.hazeSource
69-
import dev.chrisbanes.haze.materials.HazeMaterials
70-
import dev.chrisbanes.haze.rememberHazeState
71-
import kotlinx.coroutines.flow.launchIn
72-
import kotlinx.coroutines.flow.onEach
7348

7449
@Composable
7550
internal fun TokenInfoScreen(viewModel: TokenInfoViewModel) {
@@ -173,44 +148,18 @@ private fun TokenInfoScreen(
173148
if (!state.isCashReserve) {
174149
// market cap
175150
state.marketCap?.let { mcap ->
151+
val loadable = state.historicalMarketCapData[state.selectedPeriod] ?: Loadable.Loaded(emptyList())
176152
item {
177-
LaunchedEffect(state.historicalMarketCapData) {
178-
if (state.historicalMarketCapData.isNotEmpty()) {
179-
return@LaunchedEffect
180-
}
181-
182-
// generate sample data
183-
dispatch(
184-
TokenInfoViewModel.Event.OnHistoricalMarketCapDataUpdated(
185-
generateMarketCapData(
186-
mintDate = state.token!!.createdAt!!,
187-
currentMarketCap = mcap,
188-
period = state.selectedPeriod
189-
)
190-
)
191-
)
192-
}
193-
194-
MarketCapSection(
153+
MarketCapSection(
195154
modifier = Modifier
196155
.fillParentMaxWidth(),
197156
contentPadding = PaddingValues(horizontal = CodeTheme.dimens.inset),
198157
marketCap = mcap,
199158
chartEnabled = state.marketCapChartEnabled,
200159
selectedPeriod = state.selectedPeriod,
201-
rawHistoricalData = state.historicalMarketCapData,
160+
rawHistoricalData = loadable,
202161
onPeriodSelected = {
203162
dispatch(TokenInfoViewModel.Event.OnMarketCapPeriodSelected(it))
204-
// generate sample data
205-
dispatch(
206-
TokenInfoViewModel.Event.OnHistoricalMarketCapDataUpdated(
207-
generateMarketCapData(
208-
mintDate = state.token!!.createdAt!!,
209-
currentMarketCap = mcap,
210-
period = it,
211-
)
212-
)
213-
)
214163
},
215164
)
216165
}

apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/components/info/MarketCapSection.kt

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
package com.flipcash.app.tokens.internal.components.info
22

33
import android.text.format.DateFormat
4+
import androidx.compose.animation.AnimatedContent
5+
import androidx.compose.animation.Crossfade
46
import androidx.compose.animation.animateColorAsState
57
import androidx.compose.animation.core.animateFloatAsState
68
import androidx.compose.foundation.background
79
import androidx.compose.foundation.layout.Box
810
import androidx.compose.foundation.layout.Column
11+
import androidx.compose.foundation.layout.IntrinsicSize
912
import androidx.compose.foundation.layout.PaddingValues
13+
import androidx.compose.foundation.layout.Spacer
14+
import androidx.compose.foundation.layout.fillMaxSize
1015
import androidx.compose.foundation.layout.fillMaxWidth
16+
import androidx.compose.foundation.layout.height
1117
import androidx.compose.foundation.layout.padding
1218
import androidx.compose.foundation.layout.requiredHeight
1319
import androidx.compose.material.MaterialTheme
@@ -18,11 +24,13 @@ import androidx.compose.runtime.getValue
1824
import androidx.compose.runtime.mutableStateOf
1925
import androidx.compose.runtime.remember
2026
import androidx.compose.runtime.setValue
27+
import androidx.compose.ui.Alignment
2128
import androidx.compose.ui.Modifier
2229
import androidx.compose.ui.draw.alpha
2330
import androidx.compose.ui.platform.LocalContext
2431
import androidx.compose.ui.res.stringResource
2532
import androidx.compose.ui.unit.dp
33+
import com.flipcash.app.core.data.Loadable
2634
import com.flipcash.app.tokens.data.MarketCapPoint
2735
import com.flipcash.app.tokens.data.Period
2836
import com.flipcash.app.tokens.data.collapse
@@ -36,6 +44,7 @@ import com.getcode.theme.extraSmall
3644
import com.getcode.ui.components.charts.LineTrend
3745
import com.getcode.ui.components.charts.TrendType
3846
import com.getcode.ui.components.text.AnimatedNumberText
47+
import com.getcode.ui.theme.CodeCircularProgressIndicator
3948
import com.getcode.ui.utils.calculateEndPadding
4049
import com.getcode.ui.utils.calculateStartPadding
4150
import com.getcode.util.format
@@ -49,7 +58,7 @@ import kotlin.time.Instant
4958
internal fun MarketCapSection(
5059
marketCap: Fiat,
5160
chartEnabled: Boolean,
52-
rawHistoricalData: List<MarketCapPoint>,
61+
rawHistoricalData: Loadable<List<MarketCapPoint>>,
5362
selectedPeriod: Period,
5463
modifier: Modifier = Modifier,
5564
contentPadding: PaddingValues = PaddingValues(),
@@ -59,13 +68,14 @@ internal fun MarketCapSection(
5968
mutableStateOf<MarketCapPoint?>(null)
6069
}
6170

62-
val startCap by remember(selectedPeriod, rawHistoricalData) {
71+
val data = remember(rawHistoricalData) {
72+
rawHistoricalData.dataOrNull.orEmpty()
73+
}
74+
75+
val startCap by remember(selectedPeriod, data, marketCap.currencyCode) {
6376
derivedStateOf {
6477
val startQuarks =
65-
rawHistoricalData.collapse(
66-
period = selectedPeriod,
67-
targetPoints = TARGET_POINTS.toInt()
68-
).firstOrNull()?.y ?: return@derivedStateOf null
78+
data.firstOrNull()?.y ?: return@derivedStateOf null
6979
Fiat(startQuarks, marketCap.currencyCode)
7080
}
7181
}
@@ -103,26 +113,28 @@ internal fun MarketCapSection(
103113
)
104114

105115
if (chartEnabled) {
106-
marketCapDiff?.let { change ->
107-
Box(
108-
modifier = Modifier
109-
.padding(
110-
start = contentPadding.calculateStartPadding(),
111-
top = CodeTheme.dimens.grid.x1,
116+
Box(
117+
modifier = Modifier
118+
.padding(
119+
start = contentPadding.calculateStartPadding(),
120+
top = CodeTheme.dimens.grid.x1,
121+
).height(IntrinsicSize.Min),
122+
) {
123+
Crossfade(marketCapDiff) { diff ->
124+
if (diff != null) {
125+
MarketCapChangeLabel(
126+
change = diff,
127+
isVisible = highlightedCapPoint == null,
128+
period = selectedPeriod,
112129
)
113-
) {
114-
MarketCapChangeLabel(
115-
change = change,
116-
isVisible = highlightedCapPoint == null,
117-
period = selectedPeriod,
118-
)
119-
120-
HighlightedPointLabel(
121-
point = highlightedCapPoint,
122-
isVisible = highlightedCapPoint != null,
123-
period = selectedPeriod,
124-
)
130+
}
125131
}
132+
133+
HighlightedPointLabel(
134+
point = highlightedCapPoint,
135+
isVisible = highlightedCapPoint != null,
136+
period = selectedPeriod,
137+
)
126138
}
127139

128140
MarketCapChart(
@@ -134,11 +146,28 @@ internal fun MarketCapSection(
134146
start = contentPadding.calculateStartPadding(),
135147
end = contentPadding.calculateEndPadding(),
136148
),
137-
data = rawHistoricalData,
149+
data = data,
138150
trendType = TrendType.FirstVsLast,
139151
selectedPeriod = selectedPeriod,
140152
onPointHighlighted = { highlightedCapPoint = it },
141153
onPeriodSelected = onPeriodSelected,
154+
placeholder = {
155+
when (rawHistoricalData) {
156+
is Loadable.Error -> Unit
157+
is Loadable.Loaded -> Unit
158+
is Loadable.Loading -> {
159+
Box(modifier = Modifier
160+
.fillMaxWidth()
161+
.requiredHeight(240.dp)
162+
) {
163+
CodeCircularProgressIndicator(
164+
modifier = Modifier.align(Alignment.Center),
165+
color = CodeTheme.colors.dividerVariant,
166+
)
167+
}
168+
}
169+
}
170+
}
142171
)
143172
}
144173
}

apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/components/marketcap/GeneratedData.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,18 @@ internal fun generateMarketCapData(
2020
mintDate: Instant,
2121
period: Period,
2222
trend: MarketTrend = MarketTrend.Bullish
23-
): List<ChartPoint<Long, Long>> {
23+
): List<MarketCapPoint> {
2424
return generateMarketCapData(
2525
trend = trend,
2626
mintDate = mintDate,
2727
period = period,
28-
endValue = currentMarketCap.quarks,
28+
endValue = currentMarketCap.quarks.toDouble(),
2929
)
3030
}
3131

3232
internal fun generateMarketCapData(
3333
points: Int = 100,
34-
endValue: Long = 1_000_000L,
34+
endValue: Double = 1_000_000.0,
3535
trend: MarketTrend = MarketTrend.Bullish,
3636
period: Period = Period.All,
3737
mintDate: Instant = Clock.System.now() - 365.days,
@@ -88,7 +88,7 @@ internal fun generateMarketCapData(
8888
if (isAllTime && i == 0) {
8989
return@List MarketCapPoint(
9090
x = startTime,
91-
y = 0L
91+
y = 0.0
9292
)
9393
}
9494

@@ -110,7 +110,7 @@ internal fun generateMarketCapData(
110110
val value = if (i == points - 1) {
111111
endValue
112112
} else {
113-
(target + momentum).coerceAtLeast(0.0).roundToLong()
113+
(target + momentum).coerceAtLeast(0.0)
114114
}
115115

116116
MarketCapPoint(
@@ -121,7 +121,7 @@ internal fun generateMarketCapData(
121121
}
122122

123123
internal fun generateMarketCapData(
124-
endValue: Long = 1_000_000L,
124+
endValue: Double = 1_000_000.0,
125125
trend: MarketTrend = MarketTrend.Bullish,
126126
duration: Duration = 30.days,
127127
mintDate: Instant = Clock.System.now() - 365.days,
@@ -185,7 +185,7 @@ internal fun generateMarketCapData(
185185
if (isAllTime && i == 0) {
186186
return@List MarketCapPoint(
187187
x = startTime,
188-
y = 0L
188+
y = 0.0
189189
)
190190
}
191191

@@ -207,7 +207,7 @@ internal fun generateMarketCapData(
207207
val value = if (i == points - 1) {
208208
endValue
209209
} else {
210-
(target + momentum).coerceAtLeast(0.0).roundToLong()
210+
(target + momentum).coerceAtLeast(0.0)
211211
}
212212

213213
MarketCapPoint(

0 commit comments

Comments
 (0)