Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import com.flipcash.app.myaccount.MyAccountScreen
import com.flipcash.app.scanner.ScannerScreen
import com.flipcash.app.shareapp.ShareAppScreen
import com.flipcash.app.tokens.SwapFlowScreen
import com.flipcash.app.tokens.MarketCapExplainerScreen
import com.flipcash.app.tokens.TokenInfoScreen
import com.flipcash.app.tokens.TokenSelectScreen

Expand Down Expand Up @@ -138,6 +139,9 @@ fun appEntryProvider(
annotatedEntry<AppRoute.Token.Transactions>(testTag = "transaction_history_screen") { key ->
TransactionHistoryScreen(key.mint)
}
annotatedEntry<AppRoute.Token.MarketCapExplainer>(testTag = "market_cap_explainer_screen") { key ->
MarketCapExplainerScreen(key.mint)
}
annotatedEntry<AppRoute.Token.Swap> { key ->
SwapFlowScreen(route = key, resultStateRegistry = resultStateRegistry)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ sealed interface AppRoute : NavKey, Parcelable {
@Serializable
data class Transactions(val mint: Mint) : Token
@Serializable
data class MarketCapExplainer(val mint: Mint) : Token
@Serializable
data class Swap(
val purpose: SwapPurpose,
val shortfall: Fiat? = null,
Expand Down
14 changes: 14 additions & 0 deletions apps/flipcash/core/src/main/res/drawable/ic_market_cap_curve.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M2.75,17.4167C9.16667,17.4167 13.75,14.6667 19.25,4.58333"
android:strokeColor="#FFFFFF"
android:strokeWidth="2.2"
android:strokeLineCap="round" />
<path
android:pathData="M20.9,4.58333A1.65,1.65 0 1,1 17.6,4.58333A1.65,1.65 0 1,1 20.9,4.58333Z"
android:fillColor="#FFFFFF" />
</vector>
15 changes: 15 additions & 0 deletions apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1336,4 +1336,19 @@
<string name="title_tabWallet">Wallet</string>
<string name="title_tabYou">You</string>


<string name="title_howMarketCapWorks">How Market Cap Works</string>
<string name="subtitle_howMarketCapWorksRow">See what moves market cap · 1 min</string>
<string name="title_marketCapExplainerHeadline">The more people buy %1$s, the more valuable %1$s becomes.</string>
<string name="label_marketCapAmountPurchased">Amount of %1$s purchased by the community</string>
<string name="label_marketCapBalanceWouldBeWorth">Your %1$s balance would be worth</string>
<string name="label_marketCapScrubToday">%1$s Today</string>
<string name="label_marketCapTickToday">Today</string>
<string name="title_yourOwnership">Your Ownership</string>
<string name="label_underlyingTokensYouOwn">Underlying tokens you own</string>
<string name="label_currentPricePerToken">Current price per token</string>
<string name="label_shareOfCirculatingSupply">Share of circulating supply</string>
<string name="label_shareOfMaxSupply">Share of max supply</string>
<string name="label_yourCurrentAppreciation">Your current appreciation</string>
<string name="label_marketCapChartAccessibility">Price curve for %1$s with %2$s purchased</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.flipcash.app.tokens

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flipcash.app.tokens.internal.explainer.MarketCapExplainerContent
import com.flipcash.app.tokens.ui.MarketCapExplainerViewModel
import com.getcode.navigation.core.LocalCodeNavigator
import com.getcode.solana.keys.Mint
import com.getcode.ui.components.AppBarWithTitle

@Composable
fun MarketCapExplainerScreen(mint: Mint) {
val navigator = LocalCodeNavigator.current
val viewModel = hiltViewModel<MarketCapExplainerViewModel>()
val state by viewModel.stateFlow.collectAsStateWithLifecycle()

Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarWithTitle(
onBackIconClicked = { navigator.pop() },
)
MarketCapExplainerContent(state = state)
}

LaunchedEffect(viewModel, mint) {
viewModel.dispatchEvent(MarketCapExplainerViewModel.Event.OnMintProvided(mint))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ internal fun CurrencyInfoContentV2(

val isUsdf = state.isCashReserve
val isHeld = state.showTransactionHistory || state.balance.nativeAmount.isPositive
val openExplainer = {
dispatch(
TokenInfoViewModel.Event.OpenScreen(AppRoute.Token.MarketCapExplainer(token.address))
)
}

// 1. Hero bill card
item {
Expand Down Expand Up @@ -275,6 +280,16 @@ internal fun CurrencyInfoContentV2(
onPeriodSelected = {
dispatch(TokenInfoViewModel.Event.OnMarketCapPeriodSelected(it))
},
onHeaderClick = openExplainer,
)
}
item {
MarketCapExplainerRow(
modifier = Modifier
.fillParentMaxWidth()
.padding(horizontal = inset)
.padding(top = grid.x3),
onClick = openExplainer,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.flipcash.app.tokens.internal.components.info

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
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.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.flipcash.core.R
import com.getcode.theme.CodeTheme
import com.getcode.theme.extraSmall

/** Entry point to the explainer: "Row / How price works" (design node 10761:1879). */
@Composable
internal fun MarketCapExplainerRow(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val shape = CodeTheme.shapes.extraSmall
Row(
modifier = modifier
.fillMaxWidth()
.clip(shape)
.background(CodeTheme.colors.brandLight, shape)
.clickable(onClick = onClick)
.padding(14.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier.size(40.dp).background(CodeTheme.colors.surfaceVariant, CircleShape),
contentAlignment = Alignment.Center,
) {
Image(
modifier = Modifier.size(22.dp),
painter = painterResource(R.drawable.ic_market_cap_curve),
colorFilter = ColorFilter.tint(CodeTheme.colors.success),
contentDescription = null,
)
}
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = stringResource(R.string.title_howMarketCapWorks),
style = CodeTheme.typography.textMedium,
color = CodeTheme.colors.textMain,
)
Text(
text = stringResource(R.string.subtitle_howMarketCapWorksRow),
style = CodeTheme.typography.textSmall.copy(fontWeight = FontWeight.Medium),
color = CodeTheme.colors.textSecondary,
)
}
// The shared chevron is 9x16; 12dp tall keeps its proportions.
Icon(
modifier = Modifier.height(12.dp).width(6.75.dp),
painter = painterResource(R.drawable.ic_chevron_right),
contentDescription = null,
tint = CodeTheme.colors.textSecondary,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
Expand Down Expand Up @@ -77,7 +79,8 @@ internal fun MarketCapSection(
contentPadding: PaddingValues = PaddingValues(),
animateChartOpen: Boolean = true,
onRetry: () -> Unit,
onPeriodSelected: (Period) -> Unit
onPeriodSelected: (Period) -> Unit,
onHeaderClick: (() -> Unit)? = null,
) {
var highlightedCapPoint by remember {
mutableStateOf<MarketCapPoint?>(null)
Expand Down Expand Up @@ -130,12 +133,27 @@ internal fun MarketCapSection(
Column(
modifier = modifier,
) {
Text(
// Only the label, value and change pill open the explainer; the chart and range chips keep
// their own gestures.
Column(
modifier = Modifier.then(
if (onHeaderClick != null) {
Modifier.clickable(onClick = onHeaderClick)
} else {
Modifier
}
),
) {
Row(
modifier = Modifier.padding(start = contentPadding.calculateStartPadding()),
text = stringResource(R.string.subtitle_marketcap),
style = CodeTheme.typography.textMedium,
color = CodeTheme.colors.textSecondary,
)
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(R.string.subtitle_marketcap),
style = CodeTheme.typography.textMedium,
color = CodeTheme.colors.textSecondary,
)
}

AnimatedNumberText(
modifier = Modifier.padding(start = contentPadding.calculateStartPadding()),
Expand Down Expand Up @@ -186,6 +204,8 @@ internal fun MarketCapSection(
}
}

}

MarketCapChart(
modifier = Modifier
.fillMaxWidth()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.flipcash.app.tokens.internal.explainer

import kotlin.math.max

/** A tick label measured in pixels: its [centre] on the track and its text [width]. */
internal data class ExplainerLabelBox(val centre: Float, val width: Float, val isToday: Boolean)

/**
* Decides which slider labels to draw and where. Today is always drawn; every other label is
* clamped inside `[0, totalWidth]` and dropped when its bounds come within [minGap] of Today's or of
* a label already drawn. Returns the left edge of each drawn label, or null for a dropped one.
*/
internal fun layoutExplainerLabels(labels: List<ExplainerLabelBox>, totalWidth: Float, minGap: Float): List<Float?> {
val lefts = labels.map { (it.centre - it.width / 2f).coerceIn(0f, max(0f, totalWidth - it.width)) }
val drawn = mutableListOf<Int>()
val result = MutableList<Float?>(labels.size) { null }
val order = labels.indices.sortedBy { if (labels[it].isToday) 0 else 1 }
for (i in order) {
val clashes = drawn.any { j ->
lefts[i] < lefts[j] + labels[j].width + minGap && lefts[j] < lefts[i] + labels[i].width + minGap
}
if (labels[i].isToday || !clashes) {
drawn += i
result[i] = lefts[i]
}
}
return result
}
Loading
Loading