Skip to content

Commit 69e650a

Browse files
committed
feat: add market cap chart to token info stubbed with generated data
behind beta flag Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent a28375a commit 69e650a

14 files changed

Lines changed: 1121 additions & 220 deletions

File tree

apps/flipcash/features/tokens/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ dependencies {
5252
implementation(Libs.compose_material)
5353
implementation(Libs.compose_materialIconsExtended)
5454

55+
implementation(Libs.haze)
56+
implementation(Libs.haze_materials)
57+
5558
implementation(project(":apps:flipcash:shared:onramp:deeplinks"))
5659
implementation(project(":apps:flipcash:shared:shareable"))
5760
implementation(project(":apps:flipcash:shared:tokens"))
Lines changed: 85 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,51 @@
11
package com.flipcash.app.tokens.internal
22

3-
import androidx.compose.animation.Crossfade
4-
import androidx.compose.foundation.background
53
import androidx.compose.foundation.layout.Arrangement
64
import androidx.compose.foundation.layout.Box
7-
import androidx.compose.foundation.layout.Column
85
import androidx.compose.foundation.layout.PaddingValues
96
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.Spacer
108
import androidx.compose.foundation.layout.fillMaxSize
119
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.height
1211
import androidx.compose.foundation.layout.navigationBarsPadding
1312
import androidx.compose.foundation.layout.padding
14-
import androidx.compose.foundation.layout.size
1513
import androidx.compose.foundation.lazy.LazyColumn
1614
import androidx.compose.foundation.lazy.rememberLazyListState
1715
import androidx.compose.material.Divider
18-
import androidx.compose.material.Icon
1916
import androidx.compose.material.Text
2017
import androidx.compose.runtime.Composable
18+
import androidx.compose.runtime.LaunchedEffect
2119
import androidx.compose.runtime.getValue
22-
import androidx.compose.runtime.remember
2320
import androidx.compose.ui.Alignment
2421
import androidx.compose.ui.Modifier
25-
import androidx.compose.ui.res.painterResource
2622
import androidx.compose.ui.res.stringResource
27-
import androidx.compose.ui.tooling.preview.Preview
28-
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
2923
import androidx.compose.ui.unit.dp
3024
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3125
import com.flipcash.app.core.AppRoute
3226
import com.flipcash.app.core.money.RegionSelectionKind
3327
import com.flipcash.app.core.tokens.TokenSwapPurpose
34-
import com.flipcash.app.theme.FlipcashDesignSystem
28+
import com.flipcash.app.tokens.Period
3529
import com.flipcash.app.tokens.TokenInfoViewModel
30+
import com.flipcash.app.tokens.internal.components.info.MarketCapSection
31+
import com.flipcash.app.tokens.internal.components.info.MarketTrend
32+
import com.flipcash.app.tokens.internal.components.info.TokenBalance
33+
import com.flipcash.app.tokens.internal.components.info.TokenDetailsSection
34+
import com.flipcash.app.tokens.internal.components.info.generateMarketCapData
3635
import com.flipcash.features.tokens.R
37-
import com.getcode.opencode.compose.LocalExchange
38-
import com.getcode.opencode.model.financial.Fiat
3936
import com.getcode.theme.CodeTheme
40-
import com.getcode.theme.bolded
41-
import com.getcode.theme.extraSmall
42-
import com.getcode.ui.components.CodeChip
43-
import com.getcode.ui.components.text.AmountArea
44-
import com.getcode.ui.components.text.ExpandableText
4537
import com.getcode.ui.core.verticalScrollStateGradient
4638
import com.getcode.ui.theme.ButtonState
4739
import com.getcode.ui.theme.CodeButton
48-
import com.getcode.ui.theme.CodeCircularProgressIndicator
4940
import com.getcode.ui.theme.CodeScaffold
50-
import com.getcode.util.format
41+
import com.getcode.ui.utils.calculateEndPadding
42+
import com.getcode.ui.utils.calculateStartPadding
43+
import dev.chrisbanes.haze.HazeProgressive
44+
import dev.chrisbanes.haze.HazeState
45+
import dev.chrisbanes.haze.hazeEffect
46+
import dev.chrisbanes.haze.hazeSource
47+
import dev.chrisbanes.haze.materials.HazeMaterials
48+
import dev.chrisbanes.haze.rememberHazeState
5149

5250
@Composable
5351
internal fun TokenInfoScreen(viewModel: TokenInfoViewModel) {
@@ -61,9 +59,9 @@ private fun TokenInfoScreen(
6159
dispatch: (TokenInfoViewModel.Event) -> Unit
6260
) {
6361
val listState = rememberLazyListState()
64-
62+
val hazeState = rememberHazeState()
6563
CodeScaffold(
66-
bottomBar = { BottomBar(state, dispatch) }
64+
bottomBar = { BottomBar(state, hazeState, dispatch) }
6765
) { innerPadding ->
6866
Box(
6967
modifier = Modifier.verticalScrollStateGradient(
@@ -75,7 +73,11 @@ private fun TokenInfoScreen(
7573
LazyColumn(
7674
modifier = Modifier
7775
.fillMaxSize()
78-
.padding(innerPadding),
76+
.padding(
77+
start = innerPadding.calculateStartPadding(),
78+
end = innerPadding.calculateEndPadding(),
79+
)
80+
.hazeSource(hazeState),
7981
state = listState,
8082
) {
8183
item {
@@ -134,7 +136,7 @@ private fun TokenInfoScreen(
134136
color = CodeTheme.colors.textSecondary,
135137
)
136138
} else {
137-
CurrencyInfoSection(
139+
TokenDetailsSection(
138140
modifier = Modifier
139141
.fillParentMaxWidth(),
140142
state = state,
@@ -147,27 +149,80 @@ private fun TokenInfoScreen(
147149
// market cap
148150
state.marketCap?.let { mcap ->
149151
item {
152+
fun regenerateData(period: Period) {
153+
dispatch(
154+
TokenInfoViewModel.Event.OnHistoricalMarketCapDataUpdated(
155+
generateMarketCapData(
156+
period = period,
157+
trend = when (period) {
158+
Period.All -> MarketTrend.Bullish
159+
Period.Day -> MarketTrend.Bearish
160+
Period.Week -> MarketTrend.Sideways
161+
Period.Month -> MarketTrend.Volatile
162+
Period.Year -> MarketTrend.Bullish
163+
},
164+
currentMarketCap = mcap
165+
)
166+
)
167+
)
168+
}
169+
LaunchedEffect(state.historicalMarketCapData) {
170+
if (state.historicalMarketCapData.isNotEmpty()) {
171+
return@LaunchedEffect
172+
}
173+
174+
// generate sample data
175+
regenerateData(state.selectedPeriod)
176+
}
177+
150178
MarketCapSection(
151179
modifier = Modifier
152-
.fillParentMaxWidth()
153-
.padding(horizontal = CodeTheme.dimens.inset),
154-
marketCap = mcap
180+
.fillParentMaxWidth(),
181+
contentPadding = PaddingValues(horizontal = CodeTheme.dimens.inset),
182+
marketCap = mcap,
183+
chartEnabled = state.marketCapChartEnabled,
184+
selectedPeriod = state.selectedPeriod,
185+
historicalData = state.historicalMarketCapData,
186+
onPeriodSelected = {
187+
dispatch(TokenInfoViewModel.Event.OnMarketCapPeriodSelected(it))
188+
// also regenerate mcap data for sampling
189+
regenerateData(it)
190+
},
155191
)
156192
}
157193
}
158194
}
195+
196+
item { Spacer(Modifier.height(innerPadding.calculateBottomPadding())) }
159197
}
160198
}
161199
}
162200
}
163201

164202
@Composable
165-
private fun BottomBar(state: TokenInfoViewModel.State, dispatch: (TokenInfoViewModel.Event) -> Unit) {
203+
private fun BottomBar(
204+
state: TokenInfoViewModel.State,
205+
hazeState: HazeState,
206+
dispatch: (TokenInfoViewModel.Event) -> Unit
207+
) {
166208
Row(
167-
modifier = Modifier.fillMaxWidth()
209+
modifier = Modifier
210+
.fillMaxWidth()
168211
.padding(horizontal = CodeTheme.dimens.inset)
169-
.padding(bottom = CodeTheme.dimens.grid.x3)
170-
.navigationBarsPadding(),
212+
.navigationBarsPadding()
213+
.hazeEffect(
214+
state = hazeState,
215+
style = HazeMaterials.ultraThin(
216+
containerColor = CodeTheme.colors.background.copy(0.15f)
217+
)
218+
) {
219+
this.blurRadius = 20.dp
220+
this.progressive = HazeProgressive.LinearGradient(
221+
startIntensity = 0.5f,
222+
endIntensity = 1f
223+
)
224+
}
225+
.padding(vertical = CodeTheme.dimens.grid.x3),
171226
verticalAlignment = Alignment.CenterVertically,
172227
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x2),
173228
) {
@@ -226,187 +281,4 @@ private fun BottomBar(state: TokenInfoViewModel.State, dispatch: (TokenInfoViewM
226281
}
227282
}
228283
}
229-
}
230-
231-
@Composable
232-
private fun TokenBalance(
233-
modifier: Modifier = Modifier,
234-
balance: Fiat?,
235-
appreciation: Fiat,
236-
onClick: () -> Unit
237-
) {
238-
val exchange = LocalExchange.current
239-
Column(
240-
modifier = modifier
241-
.padding(horizontal = CodeTheme.dimens.inset)
242-
.padding(vertical = CodeTheme.dimens.grid.x9),
243-
) {
244-
if (balance == null) {
245-
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
246-
CodeCircularProgressIndicator()
247-
}
248-
} else {
249-
Crossfade(balance) { amount ->
250-
AmountArea(
251-
amountText = amount.formatted(),
252-
isAltCaption = false,
253-
isAltCaptionKinIcon = false,
254-
captionText = null,
255-
currencyResId = exchange.getFlagByCurrency(amount.currencyCode.name),
256-
isClickable = true,
257-
textStyle = CodeTheme.typography.displayLarge,
258-
onClick = onClick
259-
)
260-
}
261-
262-
if (appreciation > Fiat.Zero) {
263-
Crossfade(appreciation) { amount ->
264-
val relativeAmount = remember(amount) {
265-
val prefix = if (amount.isNegative) "-" else "+"
266-
val value = amount.formatted()
267-
"$prefix$value"
268-
}
269-
270-
Row(
271-
modifier = Modifier.align(Alignment.CenterHorizontally),
272-
verticalAlignment = Alignment.CenterVertically,
273-
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1),
274-
) {
275-
CodeChip(
276-
shape = CodeTheme.shapes.extraSmall,
277-
label = relativeAmount,
278-
contentPadding = PaddingValues(
279-
horizontal = 4.dp,
280-
vertical = 2.dp,
281-
),
282-
backgroundColor = CodeTheme.colors.surfaceSuccess,
283-
contentColor = CodeTheme.colors.successText,
284-
)
285-
Text(
286-
text = "from currency appreciation",
287-
style = CodeTheme.typography.textSmall.bolded(),
288-
color = CodeTheme.colors.successText,
289-
)
290-
}
291-
}
292-
}
293-
}
294-
}
295-
}
296-
297-
@Composable
298-
private fun CurrencyInfoSection(
299-
modifier: Modifier = Modifier,
300-
state: TokenInfoViewModel.State,
301-
dispatch: (TokenInfoViewModel.Event) -> Unit
302-
) {
303-
Column(
304-
modifier = modifier,
305-
) {
306-
Row(
307-
modifier = Modifier
308-
.fillMaxWidth()
309-
.padding(horizontal = CodeTheme.dimens.inset),
310-
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1),
311-
) {
312-
Icon(
313-
modifier = Modifier.size(CodeTheme.dimens.staticGrid.x4),
314-
painter = painterResource(R.drawable.ic_info_bars),
315-
contentDescription = null,
316-
)
317-
318-
Text(
319-
modifier = Modifier.weight(1f),
320-
text = stringResource(R.string.subtitle_currencyInfo),
321-
style = CodeTheme.typography.textMedium,
322-
color = CodeTheme.colors.textMain,
323-
)
324-
}
325-
326-
state.token?.createdAt?.let { mintDate ->
327-
Text(
328-
modifier = Modifier
329-
.padding(horizontal = CodeTheme.dimens.inset)
330-
.padding(top = CodeTheme.dimens.grid.x1),
331-
text = stringResource(
332-
R.string.label_mintDate,
333-
mintDate.format("MMMM dd, yyyy")
334-
),
335-
style = CodeTheme.typography.textMedium,
336-
color = CodeTheme.colors.textSecondary,
337-
)
338-
}
339-
340-
ExpandableText(
341-
modifier = Modifier
342-
.padding(
343-
top = if (state.token?.createdAt == null) {
344-
CodeTheme.dimens.grid.x1
345-
} else {
346-
CodeTheme.dimens.grid.x2
347-
}
348-
),
349-
text = state.token?.description.orEmpty(),
350-
style = CodeTheme.typography.textMedium,
351-
color = CodeTheme.colors.textSecondary,
352-
isExpanded = state.descriptionExpanded,
353-
isExpandable = false,
354-
contentPadding = PaddingValues(horizontal = CodeTheme.dimens.inset)
355-
) {
356-
dispatch(TokenInfoViewModel.Event.ExpandDescription(!state.descriptionExpanded))
357-
}
358-
359-
Divider(
360-
modifier = Modifier.padding(
361-
horizontal = CodeTheme.dimens.inset,
362-
vertical = CodeTheme.dimens.grid.x5
363-
),
364-
color = CodeTheme.colors.dividerVariant,
365-
)
366-
}
367-
}
368-
369-
@Composable
370-
private fun MarketCapSection(
371-
modifier: Modifier = Modifier,
372-
marketCap: Fiat,
373-
) {
374-
Column(
375-
modifier = modifier,
376-
) {
377-
Text(
378-
text = stringResource(R.string.subtitle_marketCap),
379-
style = CodeTheme.typography.textMedium,
380-
color = CodeTheme.colors.textSecondary,
381-
)
382-
Text(
383-
text = marketCap.formatted(),
384-
style = CodeTheme.typography.displaySmall,
385-
color = CodeTheme.colors.textMain,
386-
)
387-
388-
Divider(
389-
modifier = Modifier.padding(vertical = CodeTheme.dimens.grid.x5),
390-
color = CodeTheme.colors.dividerVariant,
391-
)
392-
}
393-
}
394-
395-
@Composable
396-
@Preview
397-
private fun PreviewTokenInfo() {
398-
FlipcashDesignSystem {
399-
Box(modifier = Modifier.background(CodeTheme.colors.background)) {
400-
ExpandableText(
401-
modifier = Modifier.padding(horizontal = CodeTheme.dimens.inset),
402-
text = LoremIpsum(words = 400).values.joinToString(" "),
403-
contentPadding = PaddingValues(horizontal = 16.dp),
404-
style = CodeTheme.typography.textMedium,
405-
color = CodeTheme.colors.textSecondary,
406-
isExpanded = false,
407-
isExpandable = false,
408-
onToggle = { }
409-
)
410-
}
411-
}
412284
}

0 commit comments

Comments
 (0)