From a0b6de22cb60b789ad8c45529e41419297100ea2 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 17 Oct 2026 15:36:56 -0400 Subject: [PATCH 1/2] feat(chat): pick a new group's currency from a full-width row The New Public Group form now has a Community Currency section above Minimum Balance Required, holding a row styled like the group profile's Token row (32dp icon, 56dp tall, chevron). The balance card keeps only the presets, and the caption names the chosen token: "People need this amount of {Token} to join this chat." In the picker, the Total Balance card is gone and Any Community Currency is the last item. It still sends an empty mints list, so the server counts USDF toward it until the contract can exclude it. Layout follows Figma node 11745:1054. --- .../core/src/main/res/values/strings.xml | 16 +- .../tipping/internal/CreateGroupViewModel.kt | 15 -- .../app/tipping/internal/GroupCurrency.kt | 4 +- .../internal/screens/NewGroupFormScreen.kt | 81 +++++--- .../screens/SelectGroupCurrencySheet.kt | 194 +++++++----------- .../tipping/internal/CreateGroupStateTest.kt | 13 -- 6 files changed, 138 insertions(+), 185 deletions(-) diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml index 1601f1cf55..f908e3268b 100644 --- a/apps/flipcash/core/src/main/res/values/strings.xml +++ b/apps/flipcash/core/src/main/res/values/strings.xml @@ -1235,21 +1235,19 @@ Minimum Balance Required People won\'t be able to join this chat if their balance is less than this amount + + People need this amount of %1$s to join this chat. + People need this amount of any community currency to join this chat. Create You Need More %1$s You have to meet the balance requirement you set to create this group - + You Need a Higher Balance - - Total Balance - - You have %1$s total · meets %2$s - - You have %1$s total · need %2$s - - You have %1$s total + + Community Currency + Any Community Currency Specific Currency This Group Name is Not Allowed These Rules Aren\'t Allowed diff --git a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/CreateGroupViewModel.kt b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/CreateGroupViewModel.kt index c33cf2c271..7dc4dabc46 100644 --- a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/CreateGroupViewModel.kt +++ b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/CreateGroupViewModel.kt @@ -37,7 +37,6 @@ import com.getcode.opencode.model.financial.Fiat import com.getcode.opencode.model.financial.Rate import com.getcode.opencode.model.financial.Token import com.getcode.opencode.model.financial.TokenWithBalance -import com.getcode.opencode.model.financial.sum import com.getcode.util.resources.ContentReader import com.getcode.util.resources.ResourceHelper import com.getcode.view.BaseViewModel @@ -152,13 +151,6 @@ internal class CreateGroupViewModel @AssistedInject constructor( val currencyName: String? get() = token?.name - /** - * Every holding added up, in USD — what an any-currency rule is measured against, and what - * the currency sheet's header card reports. - */ - val totalBalance: Fiat - get() = balances.map { it.balance }.sum() - /** * The rules the draft describes, once it describes any. * @@ -177,13 +169,6 @@ internal class CreateGroupViewModel @AssistedInject constructor( val access: GroupAccess? get() = rules?.let(::accessFor) - /** - * Whether the total meets the amount — the header card's `meets` or `need`, which it shows - * whichever currency is selected. Null until there is an amount to measure against. - */ - val allCurrenciesSatisfied: Boolean? - get() = amount?.let { accessFor(rulesFor(GroupCurrency.All, it)) is GroupAccess.Eligible } - private fun accessFor(rules: ChatRules): GroupAccess = GroupAccess.evaluate( isMember = false, rules = rules, diff --git a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/GroupCurrency.kt b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/GroupCurrency.kt index 596ea29171..915be59170 100644 --- a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/GroupCurrency.kt +++ b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/GroupCurrency.kt @@ -3,8 +3,8 @@ package com.flipcash.app.tipping.internal import com.getcode.solana.keys.Mint /** - * What a group's balance requirement is measured in — the currency sheet's header card (node - * 10370:997) or one of the tokens under it (node 10372:1015). + * What a group's balance requirement is measured in — the currency sheet's "Any Community + * Currency" entry or one of the tokens above it. * * [All] is not the absence of a choice. `MinimumBalanceRequirement.mints` left empty means "any * currency", and the server measures it against every holding added up in USD, USDF included; so it diff --git a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/NewGroupFormScreen.kt b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/NewGroupFormScreen.kt index f7c25b072a..6e29f67a75 100644 --- a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/NewGroupFormScreen.kt +++ b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/NewGroupFormScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -35,6 +36,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -311,7 +313,7 @@ private fun IdentityCard( } /** - * The balance rule: which currency, and how much of it. + * The balance rule: which currency (its own section, over the amount), and how much of it. */ @Composable private fun RequirementSection( @@ -326,6 +328,20 @@ private fun RequirementSection( ) { Text( modifier = Modifier.padding(horizontal = CodeTheme.dimens.grid.x3), + text = stringResource(R.string.title_communityCurrency), + style = LabelStyle, + color = White50, + ) + + MintRow(state = state, onClick = onChangeCurrency) + + Text( + // Its own section: the extra top inset is the gap between sections, not a label's. + modifier = Modifier.padding( + start = CodeTheme.dimens.grid.x3, + end = CodeTheme.dimens.grid.x3, + top = CodeTheme.dimens.grid.x3, + ), text = stringResource(R.string.title_minimumBalanceRequired), style = LabelStyle, color = White50, @@ -339,8 +355,6 @@ private fun RequirementSection( .padding(CodeTheme.dimens.grid.x3), verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.staticGrid.x2), ) { - MintRow(state = state, onClick = onChangeCurrency) - Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x2), @@ -384,29 +398,40 @@ private fun RequirementSection( Text( modifier = Modifier.padding(horizontal = CodeTheme.dimens.grid.x3), - text = stringResource( - if (unsatisfied) { - R.string.error_description_groupRuleNotSelfSatisfied - } else { - R.string.subtitle_groupBalanceRequirement - } - ), + text = when { + unsatisfied -> stringResource(R.string.error_description_groupRuleNotSelfSatisfied) + state.currency == GroupCurrency.All -> + stringResource(R.string.subtitle_groupBalanceRequirementAny) + // A picked token that has left the list has no name; the rule still holds for it. + else -> state.currencyName?.let { + stringResource(R.string.subtitle_groupBalanceRequirementToken, it) + } ?: stringResource(R.string.subtitle_groupBalanceRequirement) + }, style = CodeTheme.typography.caption.copy(fontSize = 13.sp, lineHeight = 17.sp), color = if (unsatisfied) CodeTheme.colors.errorText else White50, ) } } +/** + * The currency row, dressed as the group info screen's Token row: 56 high, 16 in from the edges, + * a 32 icon and the name, and a chevron in textSecondary on the 12-radius card background. + */ @Composable private fun MintRow(state: CreateGroupViewModel.State, onClick: () -> Unit) { Row( - modifier = Modifier.clickable(onClick = onClick), - horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1), + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(MintRowRadius)) + .background(White05) + .height(MintRowHeight) + .clickable(onClick = onClick) + .padding(horizontal = MintRowInset), + horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.staticGrid.x2), verticalAlignment = Alignment.CenterVertically, ) { - val allCurrencies = state.currency == GroupCurrency.All - if (allCurrencies) { - // Node 10364:1059 — the sheet's card glyph, scaled to the row. + val anyCurrency = state.currency == GroupCurrency.All + if (anyCurrency) { AllCurrenciesIcon(discSize = MintIconSize, iconSize = AllCurrenciesGlyphSize) } else { AsyncImage( @@ -424,25 +449,24 @@ private fun MintRow(state: CreateGroupViewModel.State, onClick: () -> Unit) { // A picked token whose balance has since left the list has no name to show, so the row // falls back to the sheet's own title rather than rendering a blank, tappable line. - val name = if (allCurrencies) { - stringResource(R.string.title_totalBalance) + val name = if (anyCurrency) { + stringResource(R.string.title_anyCommunityCurrency) } else { state.currencyName } Text( + modifier = Modifier.weight(1f), text = name ?: stringResource(R.string.title_selectCurrency), - style = CodeTheme.typography.textMedium.copy( - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - ), - color = if (name == null) White50 else CodeTheme.colors.textMain, + style = CodeTheme.typography.textMedium, + color = if (name == null) CodeTheme.colors.textSecondary else CodeTheme.colors.textMain, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) Icon( - painter = painterResource(R.drawable.ic_chevron_down_small), + painter = painterResource(R.drawable.ic_chevron_right), contentDescription = null, - tint = CodeTheme.colors.textMain, - modifier = Modifier.size(CodeTheme.dimens.staticGrid.x5), + tint = CodeTheme.colors.textSecondary, ) } } @@ -469,8 +493,11 @@ private fun AmountChip( private val AvatarSize = 74.dp private val CameraIconSize = 40.dp -private val MintIconSize = 20.dp -private val AllCurrenciesGlyphSize = 12.dp +private val MintIconSize = 32.dp +private val AllCurrenciesGlyphSize = 20.dp +private val MintRowHeight = 56.dp +private val MintRowInset = 16.dp +private val MintRowRadius = 12.dp private val ChipHeight = 60.dp private val LabelStyle diff --git a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt index 6fe6a57ef5..0e1437b8d4 100644 --- a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt +++ b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt @@ -2,17 +2,21 @@ package com.flipcash.app.tipping.internal.screens import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.border 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.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -24,13 +28,13 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.flipcash.app.core.tokens.TokenPurpose +import com.flipcash.app.core.ui.TokenBalanceRowStyling import com.flipcash.app.core.ui.TokenBalanceStyle import com.flipcash.app.core.ui.TokenSelectionStyle import com.flipcash.app.core.ui.rememberTokenBalanceRowStyling @@ -41,19 +45,16 @@ import com.flipcash.app.tokens.ui.TokenList import com.flipcash.app.tokens.ui.TokenListPresentation import com.flipcash.features.tipping.R import com.getcode.navigation.scenes.LocalBottomSheetDismissDispatcher -import com.getcode.opencode.model.financial.Fiat import com.getcode.theme.CodeTheme -import com.getcode.theme.White import com.getcode.theme.White05 import com.getcode.theme.White10 -import com.getcode.theme.White50 import com.getcode.ui.components.AppBarDefaults import com.getcode.ui.components.AppBarWithTitle import com.getcode.ui.theme.CodeScaffold /** - * Node 10127:118100 — which currency the group's balance requirement is denominated in, with the - * Total Balance card over the token list (node 10370:997). + * Node 10127:118100 — which currency the group's balance requirement is denominated in: the token + * list with an "Any Community Currency" entry after the last token. * * The list comes from [SelectTokenViewModel] but the *selection* does not: that view model's own * selected mint is the wallet's global one, and choosing a currency for a group being drafted must @@ -61,8 +62,8 @@ import com.getcode.ui.theme.CodeScaffold * to [CreateGroupViewModel]'s draft, and the list is driven with [TokenPurpose.Balance] — the one * purpose that isn't a `TriggersChange` and so leaves the global selection alone. * - * The card and the tokens are one selection: picking either replaces the other, and while All is - * chosen no token is marked. + * The entry and the tokens are one selection: picking either replaces the other, and while All is + * chosen no token is marked. USDF stays in the list; the contract can't say "community only". */ @Composable internal fun SelectGroupCurrencySheet(viewModel: CreateGroupViewModel) { @@ -73,6 +74,14 @@ internal fun SelectGroupCurrencySheet(viewModel: CreateGroupViewModel) { val tokenViewModel = hiltViewModel() val tokenState by tokenViewModel.stateFlow.collectAsStateWithLifecycle() + val rowStyling = rememberTokenBalanceRowStyling( + // Pill balances, dividers and a radio mark per row, which is + // TokenListPresentation.Default's treatment — the compact Sheet presentation drops + // all three, and this sheet shows all three. + balanceDisplayStyle = TokenBalanceStyle.Pill(), + selectionStyle = TokenSelectionStyle.Checkbox, + ) + CodeScaffold( topBar = { AppBarWithTitle( @@ -87,51 +96,21 @@ internal fun SelectGroupCurrencySheet(viewModel: CreateGroupViewModel) { .fillMaxWidth() .padding(padding), tokens = tokenState.tokens, - styling = rememberTokenBalanceRowStyling( - // Pill balances, dividers and a radio mark per row, which is - // TokenListPresentation.Default's treatment — the compact Sheet presentation drops - // all three, and this sheet shows all three. - balanceDisplayStyle = TokenBalanceStyle.Pill(), - selectionStyle = TokenSelectionStyle.Checkbox, - ), + styling = rowStyling, selectedToken = (state.currency as? GroupCurrency.Specific)?.mint, showSelections = true, presentation = TokenListPresentation.Default, - header = { - Column { - AllCurrenciesCard( - modifier = Modifier.padding( - start = CardMargin, - end = CardMargin, - top = CodeTheme.dimens.grid.x3, - ), - selected = state.currency == GroupCurrency.All, - total = state.totalBalance, - amount = state.amount, - satisfied = state.allCurrenciesSatisfied, - onClick = { - viewModel.dispatchEvent( - CreateGroupViewModel.Event.OnCurrencySelected(GroupCurrency.All) - ) - dismissSheet() - }, - ) - - // A label over nothing reads as a list that failed to load. - if (!tokenState.tokens.isNullOrEmpty()) { - Text( - modifier = Modifier.padding( - start = CodeTheme.dimens.inset, - end = CodeTheme.dimens.inset, - top = CodeTheme.dimens.grid.x5, - bottom = CodeTheme.dimens.grid.x1, - ), - text = stringResource(R.string.title_specificCurrency), - style = CodeTheme.typography.textSmall, - color = White50, + footer = { + AnyCurrencyRow( + selected = state.currency == GroupCurrency.All, + styling = rowStyling, + onClick = { + viewModel.dispatchEvent( + CreateGroupViewModel.Event.OnCurrencySelected(GroupCurrency.All) ) - } - } + dismissSheet() + }, + ) }, onTokenSelected = { token -> viewModel.dispatchEvent( @@ -154,78 +133,62 @@ internal fun SelectGroupCurrencySheet(viewModel: CreateGroupViewModel) { } /** - * The Total Balance card — node 10370:997 selected, 10372:1015 with a token picked instead, and - * 10372:1126 when the total falls short. - * - * The subtitle reports the total against the amount whichever currency is selected, so a creator - * choosing between All and a token can see what All would come to before picking it. + * The "Any Community Currency" entry, last in the list. It is the same radio selection as the token + * rows above it, so it takes its metrics from the same [TokenBalanceRowStyling] [TokenList] hands + * them (name style, icon size, vertical padding, check mark) and keeps the divider the rows have. + * The navigation-bar inset sits below it so the last row clears the gesture bar when scrolled to + * the end. */ @Composable -private fun AllCurrenciesCard( +private fun AnyCurrencyRow( selected: Boolean, - total: Fiat, - amount: Fiat?, - satisfied: Boolean?, + styling: TokenBalanceRowStyling, onClick: () -> Unit, modifier: Modifier = Modifier, ) { - val shape = RoundedCornerShape(CardCornerRadius) - Row( - modifier = modifier - .fillMaxWidth() - .clip(shape) - .background(White05) - .border( - width = CodeTheme.dimens.border, - color = if (selected) White50 else CardOutline, - shape = shape, - ) - .selectable(selected = selected, role = Role.RadioButton, onClick = onClick) - .padding( - horizontal = CodeTheme.dimens.inset - CardMargin, - vertical = CodeTheme.dimens.grid.x3, - ), - horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x3), - verticalAlignment = Alignment.CenterVertically, - ) { - AllCurrenciesIcon() + Column(modifier = modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .selectable(selected = selected, role = Role.RadioButton, onClick = onClick) + .padding(horizontal = CodeTheme.dimens.inset) + .padding(styling.contentPadding), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x2), + verticalAlignment = Alignment.CenterVertically, + ) { + AllCurrenciesIcon( + discSize = styling.iconSize, + iconSize = styling.iconSize * (AllCurrenciesIconSize / AllCurrenciesDiscSize), + ) - Column(modifier = Modifier.weight(1f)) { - Text( - text = stringResource(R.string.title_totalBalance), - style = CodeTheme.typography.textMedium.copy(fontWeight = FontWeight.SemiBold), - color = CodeTheme.colors.textMain, - ) + Text( + modifier = Modifier.weight(1f), + text = stringResource(R.string.title_anyCommunityCurrency), + style = styling.nameTextStyle, + color = CodeTheme.colors.textMain, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } - val totalText = total.formatted() - Text( - text = when { - amount == null -> stringResource(R.string.subtitle_totalBalance, totalText) - satisfied == false -> stringResource( - R.string.subtitle_totalBalanceNeeds, - totalText, - amount.formatted(rule = Fiat.FormattingRule.Truncated), - ) - else -> stringResource( - R.string.subtitle_totalBalanceMeets, - totalText, - amount.formatted(rule = Fiat.FormattingRule.Truncated), - ) - }, - style = CodeTheme.typography.caption, - color = if (satisfied == false) CodeTheme.colors.errorText else White50, - maxLines = 1, - overflow = TextOverflow.Ellipsis, + Image( + modifier = Modifier + .wrapContentWidth() + .padding(start = CodeTheme.dimens.grid.x3), + painter = painterResource( + if (selected) R.drawable.ic_checked else R.drawable.ic_unchecked + ), + contentDescription = null, ) } - // The token rows' own mark, so the card reads as one more option in the same list. - Image( - painter = painterResource( - if (selected) R.drawable.ic_checked else R.drawable.ic_unchecked - ), - contentDescription = null, - ) + HorizontalDivider(color = CodeTheme.colors.dividerVariant) + Spacer(Modifier.windowInsetsPadding(WindowInsets.navigationBars)) } } @@ -252,12 +215,5 @@ internal fun AllCurrenciesIcon( } } -private val CardCornerRadius = 12.dp - -// The token rows pad their contents by `inset`. The card's margin and its own padding add up to the -// same, so its icon and check line up with the rows' icons and radios. -private val CardMargin: Dp - @Composable get() = CodeTheme.dimens.grid.x2 -private val CardOutline = White.copy(alpha = 0.15f) private val AllCurrenciesDiscSize = 32.dp private val AllCurrenciesIconSize = 20.dp diff --git a/apps/flipcash/features/tipping/src/test/kotlin/com/flipcash/app/tipping/internal/CreateGroupStateTest.kt b/apps/flipcash/features/tipping/src/test/kotlin/com/flipcash/app/tipping/internal/CreateGroupStateTest.kt index a604881883..8a786da52e 100644 --- a/apps/flipcash/features/tipping/src/test/kotlin/com/flipcash/app/tipping/internal/CreateGroupStateTest.kt +++ b/apps/flipcash/features/tipping/src/test/kotlin/com/flipcash/app/tipping/internal/CreateGroupStateTest.kt @@ -157,10 +157,8 @@ class CreateGroupStateTest { balances = listOf(held(1, "BadBoys", 60.0), held(2, "Other", 50.0)), ) - assertEquals(Fiat(110.0), state.totalBalance) assertIs(state.access) assertTrue(state.canCreate) - assertEquals(true, state.allCurrenciesSatisfied) } @Test @@ -173,16 +171,5 @@ class CreateGroupStateTest { assertIs(state.access) assertFalse(state.canCreate) - assertEquals(false, state.allCurrenciesSatisfied) - } - - @Test - fun `the header card measures the total even while a token is picked`() { - // Node 10372:1015: the card still says whether All would meet the amount. - val state = state(balances = listOf(held(1, "BadBoys", 10.0), held(2, "Other", 95.0))) - - assertFalse(state.selfSatisfied) - assertEquals(true, state.allCurrenciesSatisfied) - assertNull(state(amount = null).allCurrenciesSatisfied) } } From 144e316f50a5755c3708551df67a2418dadf1738 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 17 Oct 2026 15:45:05 -0400 Subject: [PATCH 2/2] feat(chat): hide USDF from the new group currency picker Any Community Currency is the last row and already counts dollars in its USD total. Dollars aren't a community currency, so USDF is dropped from the specific-currency list in this sheet only. --- .../com/flipcash/app/tipping/internal/GroupCurrency.kt | 8 ++++++++ .../tipping/internal/screens/SelectGroupCurrencySheet.kt | 6 ++++-- .../flipcash/app/tipping/internal/CreateGroupStateTest.kt | 7 +++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/GroupCurrency.kt b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/GroupCurrency.kt index 915be59170..07dc57f882 100644 --- a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/GroupCurrency.kt +++ b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/GroupCurrency.kt @@ -23,3 +23,11 @@ internal sealed interface GroupCurrency { override val mints: List = listOf(mint) } } + +/** + * The rows the currency sheet offers as specific currencies: everything but USDF. Dollars aren't a + * community currency, and "Any Community Currency" already covers them in the USD total. The sheet + * is the only thing that sets [GroupCurrency.Specific], so a draft can't hold USDF. + */ +internal fun List.withoutUsdf(mintOf: (T) -> Mint): List = + filterNot { mintOf(it) == Mint.usdf } diff --git a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt index 0e1437b8d4..c9aae47f9f 100644 --- a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt +++ b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt @@ -40,6 +40,7 @@ import com.flipcash.app.core.ui.TokenSelectionStyle import com.flipcash.app.core.ui.rememberTokenBalanceRowStyling import com.flipcash.app.tipping.internal.CreateGroupViewModel import com.flipcash.app.tipping.internal.GroupCurrency +import com.flipcash.app.tipping.internal.withoutUsdf import com.flipcash.app.tokens.ui.SelectTokenViewModel import com.flipcash.app.tokens.ui.TokenList import com.flipcash.app.tokens.ui.TokenListPresentation @@ -63,7 +64,8 @@ import com.getcode.ui.theme.CodeScaffold * purpose that isn't a `TriggersChange` and so leaves the global selection alone. * * The entry and the tokens are one selection: picking either replaces the other, and while All is - * chosen no token is marked. USDF stays in the list; the contract can't say "community only". + * chosen no token is marked. USDF is left out of the list: dollars aren't a community currency, and + * the All entry already counts them. */ @Composable internal fun SelectGroupCurrencySheet(viewModel: CreateGroupViewModel) { @@ -95,7 +97,7 @@ internal fun SelectGroupCurrencySheet(viewModel: CreateGroupViewModel) { modifier = Modifier .fillMaxWidth() .padding(padding), - tokens = tokenState.tokens, + tokens = tokenState.tokens?.withoutUsdf { it.token.address }, styling = rowStyling, selectedToken = (state.currency as? GroupCurrency.Specific)?.mint, showSelections = true, diff --git a/apps/flipcash/features/tipping/src/test/kotlin/com/flipcash/app/tipping/internal/CreateGroupStateTest.kt b/apps/flipcash/features/tipping/src/test/kotlin/com/flipcash/app/tipping/internal/CreateGroupStateTest.kt index 8a786da52e..9651ce910a 100644 --- a/apps/flipcash/features/tipping/src/test/kotlin/com/flipcash/app/tipping/internal/CreateGroupStateTest.kt +++ b/apps/flipcash/features/tipping/src/test/kotlin/com/flipcash/app/tipping/internal/CreateGroupStateTest.kt @@ -172,4 +172,11 @@ class CreateGroupStateTest { assertIs(state.access) assertFalse(state.canCreate) } + + @Test + fun `the currency list drops USDF and keeps every other token in order`() { + val tokens = listOf(mint(1), Mint.usdf, mint(2)) + + assertEquals(listOf(mint(1), mint(2)), tokens.withoutUsdf { it }) + } }