Skip to content

Commit de5b5ca

Browse files
committed
chore: add empty state for select token screen
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent 601b7a7 commit de5b5ca

File tree

4 files changed

+127
-0
lines changed

4 files changed

+127
-0
lines changed

apps/flipcash/core/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@
285285
<string name="title_addCashToWallet">Add cash to your Flipcash wallet</string>
286286
<string name="action_addMoreCash">Add More Cash</string>
287287
<string name="title_tapAboveToAddCashToWallet">Tap above to Add Cash to your wallet</string>
288+
<string name="title_tapBelowToAddCashWallet">You don’t have any cash yet.\nTap below to add cash to your wallet</string>
288289
<string name="action_dismiss">Dismiss</string>
289290
<string name="title_success">Success</string>
290291

apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/SelectTokenScreen.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ class SelectTokenScreen(private val purpose: TokenPurpose) : ModalScreen, NamedS
6565
viewModel.dispatchEvent(SelectTokenViewModel.Event.OnPurposeChanged(purpose))
6666
}
6767

68+
LaunchedEffect(viewModel) {
69+
viewModel.eventFlow
70+
.filterIsInstance<SelectTokenViewModel.Event.OpenScreen>()
71+
.map { ScreenRegistry.get(it.route) }
72+
.onEach { navigator.push(it) }
73+
.launchIn(this)
74+
}
75+
6876
LaunchedEffect(viewModel) {
6977
viewModel.eventFlow
7078
.filterIsInstance<SelectTokenViewModel.Event.OnTokenSelected>()

apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/SelectTokenScreen.kt

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
package com.flipcash.app.tokens.internal
22

3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.PaddingValues
38
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.material.Text
412
import androidx.compose.runtime.Composable
513
import androidx.compose.runtime.getValue
614
import androidx.compose.runtime.remember
15+
import androidx.compose.ui.Alignment
716
import androidx.compose.ui.Modifier
17+
import androidx.compose.ui.res.stringResource
18+
import androidx.compose.ui.text.style.TextAlign
19+
import androidx.compose.ui.tooling.preview.Preview
820
import androidx.lifecycle.compose.collectAsStateWithLifecycle
21+
import com.flipcash.app.core.tokens.TokenPurpose
22+
import com.flipcash.app.theme.FlipcashDesignSystem
923
import com.flipcash.app.tokens.SelectTokenViewModel
1024
import com.flipcash.app.tokens.TokenList
25+
import com.flipcash.features.tokens.R
26+
import com.getcode.theme.CodeTheme
27+
import com.getcode.ui.theme.ButtonState
28+
import com.getcode.ui.theme.CodeButton
1129

1230
@Composable
1331
internal fun SelectTokenScreen(
@@ -29,6 +47,59 @@ private fun SelectTokenScreenContent(
2947
modifier = Modifier.fillMaxSize(),
3048
tokens = tokens,
3149
showFlags = true,
50+
emptyState = {
51+
Box(
52+
modifier = Modifier
53+
.fillParentMaxSize()
54+
.padding(bottom = CodeTheme.dimens.inset),
55+
contentAlignment = Alignment.Center
56+
) {
57+
Column(
58+
modifier = Modifier
59+
.fillMaxWidth()
60+
.padding(horizontal = CodeTheme.dimens.inset),
61+
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x6),
62+
horizontalAlignment = Alignment.CenterHorizontally
63+
) {
64+
Text(
65+
text = stringResource(R.string.title_tapBelowToAddCashWallet),
66+
style = CodeTheme.typography.textMedium,
67+
color = CodeTheme.colors.textSecondary,
68+
textAlign = TextAlign.Center,
69+
)
70+
71+
CodeButton(
72+
text = stringResource(R.string.action_addCash),
73+
buttonState = ButtonState.Filled,
74+
contentPadding = PaddingValues(
75+
horizontal = CodeTheme.dimens.grid.x11,
76+
vertical = CodeTheme.dimens.grid.x2
77+
)
78+
) { dispatch(SelectTokenViewModel.Event.OnAddCashClicked) }
79+
}
80+
}
81+
},
3282
onTokenSelected = { dispatch(SelectTokenViewModel.Event.OnTokenSelected(it)) }
3383
)
84+
}
85+
86+
@Composable
87+
@Preview
88+
private fun PreviewEmptyState() {
89+
FlipcashDesignSystem {
90+
Box(
91+
modifier = Modifier
92+
.fillMaxSize()
93+
.background(CodeTheme.colors.background)
94+
) {
95+
SelectTokenScreenContent(
96+
state = SelectTokenViewModel.State(
97+
purpose = TokenPurpose.Send,
98+
tokens = emptyList(),
99+
),
100+
) {
101+
102+
}
103+
}
104+
}
34105
}

apps/flipcash/shared/tokens/src/main/kotlin/SelectTokenViewModel.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package com.flipcash.app.tokens
22

33
import androidx.lifecycle.viewModelScope
4+
import com.flipcash.app.core.AppRoute
45
import com.flipcash.app.core.tokens.TokenPurpose
6+
import com.flipcash.services.analytics.AnalyticsEvent
7+
import com.flipcash.services.analytics.FlipcashAnalyticsService
8+
import com.flipcash.services.internal.model.thirdparty.OnRampProvider
9+
import com.flipcash.services.internal.model.thirdparty.OnRampType
10+
import com.flipcash.services.user.AuthState
11+
import com.flipcash.services.user.UserManager
512
import com.getcode.opencode.controllers.TokenController
613
import com.getcode.opencode.exchange.Exchange
714
import com.getcode.opencode.model.financial.Fiat
@@ -13,39 +20,59 @@ import com.getcode.opencode.model.financial.toFiat
1320
import com.getcode.view.BaseViewModel2
1421
import dagger.hilt.android.lifecycle.HiltViewModel
1522
import kotlinx.coroutines.flow.combine
23+
import kotlinx.coroutines.flow.filter
1624
import kotlinx.coroutines.flow.filterIsInstance
1725
import kotlinx.coroutines.flow.flatMapLatest
1826
import kotlinx.coroutines.flow.flowOf
1927
import kotlinx.coroutines.flow.launchIn
2028
import kotlinx.coroutines.flow.map
29+
import kotlinx.coroutines.flow.mapNotNull
2130
import kotlinx.coroutines.flow.onEach
2231
import javax.inject.Inject
2332

2433
@HiltViewModel
2534
class SelectTokenViewModel @Inject constructor(
35+
userManager: UserManager,
2636
tokenController: TokenController,
2737
exchange: Exchange,
38+
analytics: FlipcashAnalyticsService,
2839
): BaseViewModel2<SelectTokenViewModel.State, SelectTokenViewModel.Event>(
2940
initialState = State(purpose = TokenPurpose.Balance),
3041
updateStateForEvent = updateStateForEvent
3142
) {
3243

3344
data class State(
3445
val purpose: TokenPurpose,
46+
val preferredOnRampProvider: OnRampProvider? = null,
3547
val tokens: List<TokenWithLocalizedBalance>? = null,
3648
) {
3749
val totalBalance: LocalFiat
3850
get() = tokens.orEmpty().map { it.balance }.sum()
3951
}
4052

4153
sealed interface Event {
54+
data class OnPreferredOnRampProviderChanged(val provider: OnRampProvider?) : Event
55+
4256
data class OnPurposeChanged(val purpose: TokenPurpose) : Event
4357
data class OnTokensUpdated(val tokens: List<TokenWithLocalizedBalance>) : Event
4458

4559
data class OnTokenSelected(val token: Token): Event
60+
61+
data object OnAddCashClicked: Event
62+
data object OpenOnRampAmountModal: Event
63+
data class OpenScreen(val route: AppRoute): Event
4664
}
4765

4866
init {
67+
userManager.state
68+
.filter { it.authState is AuthState.LoggedInWithUser }
69+
.mapNotNull { it.flags }
70+
.map { it.preferredOnRampProvider }
71+
.onEach { provider ->
72+
dispatchEvent(Event.OnPreferredOnRampProviderChanged(provider))
73+
}
74+
.launchIn(viewModelScope)
75+
4976
eventFlow
5077
.filterIsInstance<Event.OnPurposeChanged>()
5178
.map { it.purpose }
@@ -83,14 +110,34 @@ class SelectTokenViewModel @Inject constructor(
83110
}
84111
}.onEach { dispatchEvent(Event.OnTokensUpdated(it)) }
85112
.launchIn(viewModelScope)
113+
114+
eventFlow
115+
.filterIsInstance<Event.OnAddCashClicked>()
116+
.onEach {
117+
analytics.openOnramp(AnalyticsEvent.OnRampOpenEvent.Balance)
118+
val provider = stateFlow.value.preferredOnRampProvider
119+
if (provider is OnRampProvider.Coinbase && provider.type == OnRampType.Virtual) {
120+
// has coinbase provider supporting google pay - pop selection for quick add
121+
dispatchEvent(Event.OpenOnRampAmountModal)
122+
} else {
123+
// route to provider list
124+
dispatchEvent(Event.OpenScreen(AppRoute.OnRamp.ProviderList(AppRoute.Sheets.Wallet)))
125+
}
126+
}.launchIn(viewModelScope)
86127
}
87128

88129
companion object {
89130
val updateStateForEvent: (Event) -> ((State) -> State) = { event ->
90131
when (event) {
132+
is Event.OnPreferredOnRampProviderChanged -> { state ->
133+
state.copy(preferredOnRampProvider = event.provider)
134+
}
91135
is Event.OnPurposeChanged -> { state -> state.copy(purpose = event.purpose) }
92136
is Event.OnTokensUpdated -> { state -> state.copy(tokens = event.tokens) }
93137
is Event.OnTokenSelected -> { state -> state }
138+
is Event.OnAddCashClicked -> { state -> state }
139+
is Event.OpenOnRampAmountModal -> { state -> state }
140+
is Event.OpenScreen -> { state -> state }
94141
}
95142
}
96143
}

0 commit comments

Comments
 (0)