Skip to content

Commit eb2731a

Browse files
committed
feat(flipcash): add empty state for balance/activity feed
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent d625945 commit eb2731a

File tree

3 files changed

+181
-18
lines changed

3 files changed

+181
-18
lines changed

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/BalanceScreen.kt

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ import android.os.Parcelable
44
import androidx.compose.foundation.layout.Column
55
import androidx.compose.foundation.layout.fillMaxSize
66
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.LaunchedEffect
78
import androidx.compose.ui.Alignment
89
import androidx.compose.ui.Modifier
910
import androidx.compose.ui.res.stringResource
1011
import androidx.lifecycle.Lifecycle
12+
import cafe.adriel.voyager.core.registry.ScreenRegistry
1113
import cafe.adriel.voyager.core.screen.ScreenKey
1214
import cafe.adriel.voyager.core.screen.uniqueScreenKey
13-
import com.flipcash.app.balance.internal.BalanceScreenContent
15+
import com.flipcash.app.balance.internal.BalanceScreen
1416
import com.flipcash.app.balance.internal.BalanceViewModel
17+
import com.flipcash.app.core.NavScreenProvider
18+
import com.flipcash.app.core.money.CurrencySelectionKind
1519
import com.flipcash.core.R
1620
import com.getcode.navigation.core.LocalCodeNavigator
1721
import com.getcode.navigation.extensions.getActivityScopedViewModel
@@ -20,6 +24,9 @@ import com.getcode.navigation.screens.NamedScreen
2024
import com.getcode.ui.components.AppBarDefaults
2125
import com.getcode.ui.components.AppBarWithTitle
2226
import com.getcode.ui.utils.RepeatOnLifecycle
27+
import kotlinx.coroutines.flow.filterIsInstance
28+
import kotlinx.coroutines.flow.launchIn
29+
import kotlinx.coroutines.flow.onEach
2330
import kotlinx.parcelize.IgnoredOnParcel
2431
import kotlinx.parcelize.Parcelize
2532

@@ -50,11 +57,37 @@ class BalanceScreen: ModalScreen, NamedScreen, Parcelable {
5057
)
5158

5259
val viewModel = getActivityScopedViewModel<BalanceViewModel>()
53-
BalanceScreenContent(viewModel)
60+
BalanceScreen(viewModel)
5461

5562
RepeatOnLifecycle(Lifecycle.State.RESUMED) {
5663
viewModel.dispatchEvent(BalanceViewModel.Event.ResetSelections)
5764
}
65+
66+
LaunchedEffect(viewModel) {
67+
viewModel.eventFlow
68+
.filterIsInstance<BalanceViewModel.Event.OpenCurrencySelection>()
69+
.onEach {
70+
navigator.push(
71+
ScreenRegistry.get(
72+
NavScreenProvider.HomeScreen.CurrencySelection(
73+
CurrencySelectionKind.Balance
74+
)
75+
)
76+
)
77+
}.launchIn(this)
78+
}
79+
80+
LaunchedEffect(viewModel) {
81+
viewModel.eventFlow
82+
.filterIsInstance<BalanceViewModel.Event.OpenDeposit>()
83+
.onEach {
84+
navigator.push(
85+
ScreenRegistry.get(
86+
NavScreenProvider.HomeScreen.Menu.Transfers.Deposit
87+
)
88+
)
89+
}.launchIn(this)
90+
}
5891
}
5992
}
6093
}
Lines changed: 141 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,138 @@
11
package com.flipcash.app.balance.internal
22

3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
36
import androidx.compose.foundation.layout.Column
47
import androidx.compose.foundation.layout.Spacer
58
import androidx.compose.foundation.layout.WindowInsets
69
import androidx.compose.foundation.layout.fillMaxWidth
710
import androidx.compose.foundation.layout.navigationBars
11+
import androidx.compose.foundation.layout.padding
812
import androidx.compose.foundation.layout.windowInsetsPadding
913
import androidx.compose.foundation.lazy.LazyColumn
1014
import androidx.compose.foundation.lazy.itemsIndexed
1115
import androidx.compose.foundation.lazy.rememberLazyListState
1216
import androidx.compose.material.Divider
17+
import androidx.compose.material.Text
1318
import androidx.compose.runtime.Composable
19+
import androidx.compose.runtime.CompositionLocalProvider
1420
import androidx.compose.runtime.collectAsState
1521
import androidx.compose.runtime.getValue
22+
import androidx.compose.ui.Alignment
1623
import androidx.compose.ui.Modifier
24+
import androidx.compose.ui.platform.LocalContext
25+
import androidx.compose.ui.text.style.TextAlign
26+
import androidx.compose.ui.tooling.preview.Preview
27+
import androidx.paging.LoadState
28+
import androidx.paging.PagingData
29+
import androidx.paging.compose.LazyPagingItems
1730
import androidx.paging.compose.collectAsLazyPagingItems
1831
import androidx.paging.compose.itemKey
1932
import cafe.adriel.voyager.core.registry.ScreenRegistry
2033
import com.flipcash.app.balance.internal.components.BalanceHeader
2134
import com.flipcash.app.balance.internal.components.FeedItem
2235
import com.flipcash.app.core.NavScreenProvider
36+
import com.flipcash.app.core.feed.ActivityFeedMessage
2337
import com.flipcash.app.core.money.CurrencySelectionKind
38+
import com.flipcash.app.core.transfers.TransferDirection
39+
import com.flipcash.app.theme.FlipcashDesignSystem
2440
import com.getcode.navigation.core.LocalCodeNavigator
41+
import com.getcode.opencode.compose.ExchangeStub
42+
import com.getcode.opencode.compose.LocalExchange
43+
import com.getcode.opencode.model.financial.CurrencyCode
44+
import com.getcode.opencode.model.financial.LocalFiat
45+
import com.getcode.opencode.model.financial.Rate
46+
import com.getcode.opencode.model.financial.toFiat
2547
import com.getcode.theme.CodeTheme
2648
import com.getcode.ui.core.verticalScrollStateGradient
49+
import com.getcode.ui.theme.ButtonState
50+
import com.getcode.ui.theme.CodeButton
51+
import kotlinx.coroutines.flow.flowOf
52+
import kotlin.collections.emptyList
2753

2854
@Composable
29-
internal fun BalanceScreenContent(viewModel: BalanceViewModel) {
55+
internal fun BalanceScreen(viewModel: BalanceViewModel) {
3056
val state by viewModel.stateFlow.collectAsState()
31-
val navigator = LocalCodeNavigator.current
32-
3357
val feed = viewModel.feed.collectAsLazyPagingItems()
3458

59+
BalanceScreenContent(
60+
state = state,
61+
feed = feed,
62+
dispatchEvent = viewModel::dispatchEvent
63+
)
64+
}
65+
66+
@Composable
67+
private fun BalanceScreenContent(
68+
state: BalanceViewModel.State,
69+
feed: LazyPagingItems<ActivityFeedMessage>,
70+
dispatchEvent: (BalanceViewModel.Event) -> Unit
71+
) {
3572
Column {
3673
BalanceHeader(
3774
modifier = Modifier
3875
.fillMaxWidth(),
3976
balance = state.balance
4077
) {
41-
navigator.push(ScreenRegistry.get(NavScreenProvider.HomeScreen.CurrencySelection(CurrencySelectionKind.Balance)))
78+
dispatchEvent(BalanceViewModel.Event.OpenCurrencySelection)
4279
}
4380

44-
val listState = rememberLazyListState()
45-
LazyColumn(
46-
modifier = Modifier
47-
.weight(1f)
48-
.verticalScrollStateGradient(
49-
scrollState = listState,
50-
color = CodeTheme.colors.background,
51-
showAtEnd = true
52-
),
53-
state = listState
54-
) {
81+
FeedList(
82+
modifier = Modifier.weight(1f),
83+
state = state,
84+
feed = feed,
85+
dispatchEvent = dispatchEvent
86+
)
87+
}
88+
}
89+
90+
@Composable
91+
private fun FeedList(
92+
modifier: Modifier = Modifier,
93+
state: BalanceViewModel.State,
94+
feed: LazyPagingItems<ActivityFeedMessage>,
95+
dispatchEvent: (BalanceViewModel.Event) -> Unit
96+
) {
97+
val listState = rememberLazyListState()
98+
LazyColumn(
99+
modifier = modifier
100+
.verticalScrollStateGradient(
101+
scrollState = listState,
102+
color = CodeTheme.colors.background,
103+
showAtEnd = true
104+
),
105+
state = listState
106+
) {
107+
if (feed.itemCount == 0 && feed.loadState.append is LoadState.NotLoading) {
108+
item {
109+
Box(
110+
modifier = Modifier.fillParentMaxSize().padding(bottom = CodeTheme.dimens.inset),
111+
contentAlignment = Alignment.Center
112+
) {
113+
Column(
114+
modifier = Modifier.fillMaxWidth().padding(horizontal = CodeTheme.dimens.inset),
115+
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x6)
116+
) {
117+
Text(
118+
modifier = Modifier.fillMaxWidth(),
119+
text = "Ask a friend to give you some Flipcash, or deposit USDC from your crypto exchange or other crypto wallet",
120+
style = CodeTheme.typography.textMedium,
121+
color = CodeTheme.colors.textMain,
122+
textAlign = TextAlign.Center,
123+
)
124+
125+
CodeButton(
126+
modifier = Modifier.fillMaxWidth(),
127+
text = "Deposit USDC",
128+
buttonState = ButtonState.Filled
129+
) {
130+
dispatchEvent(BalanceViewModel.Event.OpenDeposit)
131+
}
132+
}
133+
}
134+
}
135+
} else {
55136
items(feed.itemCount, key = feed.itemKey { item -> item.id }) { index ->
56137
val message = feed[index] ?: return@items
57138
FeedItem(
@@ -61,7 +142,7 @@ internal fun BalanceScreenContent(viewModel: BalanceViewModel) {
61142
message = message,
62143
canViewDetails = state.canViewDetails,
63144
isExpanded = state.expandedItem == message.id,
64-
dispatch = viewModel::dispatchEvent
145+
dispatch = dispatchEvent
65146
)
66147

67148
if (index < feed.itemCount - 1) {
@@ -74,4 +155,48 @@ internal fun BalanceScreenContent(viewModel: BalanceViewModel) {
74155
}
75156
}
76157
}
158+
}
159+
160+
private val cadUsdRate = Rate(fx = 1.371881, currency = CurrencyCode.CAD)
161+
private val usdCadRate = Rate(fx = 1.0 / 1.371881, currency = CurrencyCode.CAD)
162+
163+
@Preview
164+
@Composable
165+
private fun Preview_BalanceScreen_Empty() {
166+
FlipcashDesignSystem {
167+
CompositionLocalProvider(
168+
LocalExchange provides ExchangeStub(
169+
providedRates = mapOf(
170+
CurrencyCode.CAD to cadUsdRate,
171+
CurrencyCode.USD to usdCadRate
172+
),
173+
context = LocalContext.current
174+
),
175+
) {
176+
Box(modifier = Modifier.background(CodeTheme.colors.background)) {
177+
BalanceScreenContent(
178+
state = BalanceViewModel.State(
179+
balance = LocalFiat(0.toFiat())
180+
),
181+
feed = flowOf(PagingData.empty<ActivityFeedMessage>()).collectAsLazyPagingItems(),
182+
dispatchEvent = {}
183+
)
184+
}
185+
}
186+
}
187+
}
188+
189+
@Preview
190+
@Composable
191+
private fun Preview_FeedList_Empty() {
192+
FlipcashDesignSystem {
193+
Box(modifier = Modifier.background(CodeTheme.colors.background)) {
194+
FeedList(
195+
state = BalanceViewModel.State(
196+
balance = LocalFiat(0.toFiat())
197+
),
198+
feed = flowOf(PagingData.empty<ActivityFeedMessage>()).collectAsLazyPagingItems()
199+
) { }
200+
}
201+
}
77202
}

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceViewModel.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ internal class BalanceViewModel @Inject constructor(
5454
data object ResetSelections : Event
5555
data class OnCancelRequested(val message: ActivityFeedMessage) : Event
5656
data class CancelTransfer(val vault: PublicKey) : Event
57+
58+
data object OpenCurrencySelection : Event
59+
data object OpenDeposit : Event
5760
}
5861

5962
init {
@@ -138,6 +141,8 @@ internal class BalanceViewModel @Inject constructor(
138141
internal companion object {
139142
val updateStateForEvent: (Event) -> ((State) -> State) = { event ->
140143
when (event) {
144+
Event.OpenCurrencySelection -> { state -> state }
145+
Event.OpenDeposit -> { state -> state }
141146
Event.ResetSelections -> { state -> state }
142147
is Event.OnCancelRequested -> { state -> state }
143148
is Event.CancelTransfer -> { state -> state }

0 commit comments

Comments
 (0)