11package com.flipcash.app.tokens.internal
22
3- import androidx.compose.animation.Crossfade
4- import androidx.compose.foundation.background
53import androidx.compose.foundation.layout.Arrangement
64import androidx.compose.foundation.layout.Box
7- import androidx.compose.foundation.layout.Column
85import androidx.compose.foundation.layout.PaddingValues
96import androidx.compose.foundation.layout.Row
7+ import androidx.compose.foundation.layout.Spacer
108import androidx.compose.foundation.layout.fillMaxSize
119import androidx.compose.foundation.layout.fillMaxWidth
10+ import androidx.compose.foundation.layout.height
1211import androidx.compose.foundation.layout.navigationBarsPadding
1312import androidx.compose.foundation.layout.padding
14- import androidx.compose.foundation.layout.size
1513import androidx.compose.foundation.lazy.LazyColumn
1614import androidx.compose.foundation.lazy.rememberLazyListState
1715import androidx.compose.material.Divider
18- import androidx.compose.material.Icon
1916import androidx.compose.material.Text
2017import androidx.compose.runtime.Composable
18+ import androidx.compose.runtime.LaunchedEffect
2119import androidx.compose.runtime.getValue
22- import androidx.compose.runtime.remember
2320import androidx.compose.ui.Alignment
2421import androidx.compose.ui.Modifier
25- import androidx.compose.ui.res.painterResource
2622import androidx.compose.ui.res.stringResource
27- import androidx.compose.ui.tooling.preview.Preview
28- import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
2923import androidx.compose.ui.unit.dp
3024import androidx.lifecycle.compose.collectAsStateWithLifecycle
3125import com.flipcash.app.core.AppRoute
3226import com.flipcash.app.core.money.RegionSelectionKind
3327import com.flipcash.app.core.tokens.TokenSwapPurpose
34- import com.flipcash.app.theme.FlipcashDesignSystem
28+ import com.flipcash.app.tokens.Period
3529import 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
3635import com.flipcash.features.tokens.R
37- import com.getcode.opencode.compose.LocalExchange
38- import com.getcode.opencode.model.financial.Fiat
3936import 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
4537import com.getcode.ui.core.verticalScrollStateGradient
4638import com.getcode.ui.theme.ButtonState
4739import com.getcode.ui.theme.CodeButton
48- import com.getcode.ui.theme.CodeCircularProgressIndicator
4940import 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
5351internal 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