Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package com.flipcash.app.core.ui

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.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
Expand All @@ -15,17 +18,35 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.getcode.theme.CodeTheme
import com.getcode.theme.extraSmall
import androidx.compose.foundation.clickable

/**
* Controls how a [TileButton] arranges its icon and label.
*/
enum class TileButtonStyle {
/** Icon centered above the label — the default compact tile. */
Centered,

/**
* Icon pinned to the top-start and the label to the bottom-start, spread across the
* height of the tile. Used for the larger call-to-action tiles.
*/
Spread,
}

@Composable
fun TileButton(
text: String,
icon: Painter,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(vertical = CodeTheme.dimens.grid.x6),
style: TileButtonStyle = TileButtonStyle.Centered,
contentPadding: PaddingValues = when (style) {
TileButtonStyle.Centered -> PaddingValues(vertical = CodeTheme.dimens.grid.x6)
TileButtonStyle.Spread -> PaddingValues(CodeTheme.dimens.grid.x4)
},
onClick: () -> Unit,
) {
Box(
Expand All @@ -39,21 +60,52 @@ fun TileButton(
.padding(contentPadding),
contentAlignment = Alignment.Center,
) {
Column(
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
modifier = Modifier.size(32.dp),
painter = icon,
contentDescription = null,
colorFilter = ColorFilter.tint(CodeTheme.colors.textMain),
)
Text(
text = text,
style = CodeTheme.typography.textMedium,
color = CodeTheme.colors.textMain,
)
when (style) {
TileButtonStyle.Centered -> {
Column(
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1),
horizontalAlignment = Alignment.CenterHorizontally,
) {
TileButtonIcon(icon)
Text(
text = text,
style = CodeTheme.typography.textMedium,
color = CodeTheme.colors.textMain,
textAlign = TextAlign.Center,
)
}
}

TileButtonStyle.Spread -> {
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.Start,
) {
TileButtonIcon(icon, style)
Text(
text = text,
style = CodeTheme.typography.textMedium,
color = CodeTheme.colors.textMain,
)
}
}
}
}
}
}

@Composable
private fun TileButtonIcon(icon: Painter, style: TileButtonStyle = TileButtonStyle.Centered) {
val size = when (style) {
TileButtonStyle.Centered -> 32.dp
TileButtonStyle.Spread -> 24.dp
}
Image(
modifier = Modifier.size(size),
painter = icon,
contentDescription = null,
colorFilter = ColorFilter.tint(CodeTheme.colors.textMain),
)
}
26 changes: 26 additions & 0 deletions apps/flipcash/core/src/main/res/drawable/ic_globe.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,21.25C17.109,21.25 21.25,17.109 21.25,12C21.25,6.891 17.109,2.75 12,2.75C6.891,2.75 2.75,6.891 2.75,12C2.75,17.109 6.891,21.25 12,21.25Z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
<path
android:pathData="M12,21.25C13.933,21.25 15.5,17.109 15.5,12C15.5,6.891 13.933,2.75 12,2.75C10.067,2.75 8.5,6.891 8.5,12C8.5,17.109 10.067,21.25 12,21.25Z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
<path
android:pathData="M3.5,9.25H20.5"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
<path
android:pathData="M3.5,14.75H20.5"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package com.flipcash.app.balance.internal
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -33,9 +35,11 @@ import com.flipcash.app.core.AppRoute
import com.flipcash.app.core.ui.AppreciationStyle
import com.flipcash.app.core.ui.TokenCardStack
import com.flipcash.app.balance.internal.components.BalanceHeader
import com.flipcash.app.balance.internal.components.OnboardingFunnel
import com.flipcash.app.balance.internal.components.OnboardingItem
import com.flipcash.app.balance.internal.components.NewUserTutorial
import com.flipcash.app.balance.internal.components.TutorialItem
import com.flipcash.app.core.navigation.LocalTabBarPadding
import com.flipcash.app.core.ui.TileButton
import com.flipcash.app.core.ui.TileButtonStyle
import com.flipcash.app.tokens.ui.SelectTokenViewModel
import com.flipcash.features.balance.R
import com.flipcash.shared.transactionhistory.ActivityFeedRow
Expand Down Expand Up @@ -80,7 +84,7 @@ internal fun WalletScreenContent(
start = CodeTheme.dimens.inset,
end = CodeTheme.dimens.inset,
bottom = LocalTabBarPadding.current.calculateBottomPadding() + CodeTheme.dimens.grid.x12,
)
),
) {
item {
// v2 wallet header: 96 dp top / 44 dp bottom per Figma node 8966:1578.
Expand All @@ -101,20 +105,20 @@ internal fun WalletScreenContent(
Spacer(Modifier.height(CodeTheme.dimens.grid.x6))
}

if (!balanceState.isOnboardingComplete) {
if (!balanceState.isNewUserTutorialComplete) {
item {
OnboardingFunnel(
NewUserTutorial(
modifier = Modifier.fillMaxWidth()
.padding(bottom = CodeTheme.dimens.grid.x5),
title = stringResource(R.string.title_tipOnboarding),
items = balanceState.onboardingItems,
) { item ->
when (item) {
is OnboardingItem.AddMoney -> {
is TutorialItem.AddMoney -> {
dispatchEvent(WalletViewModel.Event.PresentDepositOptions)
}
is OnboardingItem.ScanTipCard -> {

is TutorialItem.ScanTipCard -> {
dispatchEvent(WalletViewModel.Event.OpenScreen(AppRoute.Main.Scanner))
}
}
}
Expand All @@ -139,34 +143,6 @@ internal fun WalletScreenContent(
}
}

if (balanceState.hasAddedMoney) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { dispatchEvent(WalletViewModel.Event.PresentDepositOptions) })
.padding(vertical = CodeTheme.dimens.inset),
contentAlignment = Alignment.Center,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1),
) {
Icon(
painter = rememberVectorPainter(Icons.Outlined.AddCircleOutline),
contentDescription = null,
tint = CodeTheme.colors.textSecondary,
)
Text(
text = stringResource(R.string.action_addMoney),
style = CodeTheme.typography.textMedium,
color = CodeTheme.colors.textSecondary,
)
}
}
}
}

if (balanceState.transactions.isNotEmpty()) {
item(key = "recentHeader") {
// Tap the header to dive into the full paged activity history.
Expand Down Expand Up @@ -206,5 +182,44 @@ internal fun WalletScreenContent(
ActivityFeedRow(item = item, modifier = Modifier.fillMaxWidth())
}
}

if (balanceState.hasAddedMoney) {
item {
Spacer(Modifier.height(CodeTheme.dimens.grid.x6))
}

item {
// Equal-height tiles: the taller (wrapping) label drives the row height and the
// shorter tile stretches to match, so each tile can spread its icon/label vertically.
Row(
modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Min),
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x2),
) {
TileButton(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
style = TileButtonStyle.Spread,
text = stringResource(R.string.action_addMoney),
icon = rememberVectorPainter(Icons.Outlined.AddCircleOutline),
) {
dispatchEvent(WalletViewModel.Event.PresentDepositOptions)
}

TileButton(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
style = TileButtonStyle.Spread,
text = stringResource(R.string.action_discoverCurrencies),
icon = painterResource(R.drawable.ic_globe),
) {
dispatchEvent(WalletViewModel.Event.OpenScreen(AppRoute.Token.Discovery))
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.flipcash.app.balance.internal
import androidx.lifecycle.viewModelScope
import com.flipcash.app.analytics.Analytics
import com.flipcash.app.analytics.FlipcashAnalyticsService
import com.flipcash.app.balance.internal.components.OnboardingItem
import com.flipcash.app.balance.internal.components.TutorialItem
import com.flipcash.app.core.AppRoute
import com.flipcash.shared.transactionhistory.ActivityFeedCoordinator
import com.flipcash.shared.transactionhistory.TransactionListItem
Expand All @@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
Expand All @@ -42,7 +41,7 @@ internal class WalletViewModel @Inject constructor(
) {
data class State(
val preferredOnRampProvider: OnRampProvider.Defined? = null,
val onboardingItems: List<OnboardingItem> = emptyList(),
val onboardingItems: List<TutorialItem> = emptyList(),
/**
* Preview of the most recent unified cross-token activity — at most [RECENT_PREVIEW_COUNT]
* rows. The coordinator owns the mapping and enforces the limit; the full paged history is a
Expand All @@ -51,14 +50,14 @@ internal class WalletViewModel @Inject constructor(
val transactions: List<TransactionListItem> = emptyList(),
) {
val hasAddedMoney: Boolean
get() = onboardingItems.find { it is OnboardingItem.AddMoney }?.isCompleted == true
get() = onboardingItems.find { it is TutorialItem.AddMoney }?.isCompleted == true

val isOnboardingComplete: Boolean
val isNewUserTutorialComplete: Boolean
get() = onboardingItems.all { it.isCompleted }
}

sealed interface Event {
data class OnOnboardingItemsUpdated(val items: List<OnboardingItem>): Event
data class OnOnboardingItemsUpdated(val items: List<TutorialItem>): Event
data class OnTransactionsUpdated(val transactions: List<TransactionListItem>) : Event
data class OnPreferredOnRampProviderChanged(val provider: OnRampProvider.Defined?) : Event

Expand Down Expand Up @@ -97,8 +96,8 @@ internal class WalletViewModel @Inject constructor(
chatCoordinator.hasEverTipped(),
) { hasAddedMoney, hasTipped ->
listOf(
OnboardingItem.AddMoney(isCompleted = hasAddedMoney),
OnboardingItem.ScanTipCard(isCompleted = hasTipped),
TutorialItem.AddMoney(isCompleted = hasAddedMoney),
TutorialItem.ScanTipCard(isCompleted = hasTipped),
)
}
.onEach { items -> dispatchEvent(Event.OnOnboardingItemsUpdated(items)) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import androidx.compose.ui.util.fastForEach
import com.flipcash.features.balance.R
import com.getcode.theme.CodeTheme

sealed interface OnboardingItem {
sealed interface TutorialItem {
val title: String
@Composable get
val description: String
Expand All @@ -39,7 +39,7 @@ sealed interface OnboardingItem {
@Composable get
val isCompleted: Boolean

class AddMoney(override val isCompleted: Boolean) : OnboardingItem {
class AddMoney(override val isCompleted: Boolean) : TutorialItem {
override val title: String
@Composable get() = stringResource(R.string.title_addMoney)
override val description: String
Expand All @@ -49,7 +49,7 @@ sealed interface OnboardingItem {

}

class ScanTipCard(override val isCompleted: Boolean) : OnboardingItem {
class ScanTipCard(override val isCompleted: Boolean) : TutorialItem {
override val title: String
@Composable get() = stringResource(R.string.title_scanTipCard)
override val description: String
Expand All @@ -60,11 +60,11 @@ sealed interface OnboardingItem {
}

@Composable
fun OnboardingFunnel(
fun NewUserTutorial(
title: String,
items: List<OnboardingItem>,
items: List<TutorialItem>,
modifier: Modifier = Modifier,
onItemClicked: (OnboardingItem) -> Unit,
onItemClicked: (TutorialItem) -> Unit,
) {
val completedCount = remember(items) { items.count { it.isCompleted } }

Expand Down Expand Up @@ -110,7 +110,7 @@ fun OnboardingFunnel(

@Composable
private fun OnboardingItemRow(
item: OnboardingItem,
item: TutorialItem,
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
Expand Down
Loading
Loading