Skip to content

Commit c4e3571

Browse files
committed
feat(chats): add the empty state to the v2 Chats tab
An empty Chats tab rendered as a blank screen under the title. Replace it with the designed prompt (Figma 9340:2746): the outlined bubble mark, "No Chats Yet", and a line pointing at the two ways a conversation starts. To tell "no chats" from "not loaded yet", `tipChats` becomes a `Loadable`, so the prompt only appears once the chat feed has actually emitted rather than flashing over a list that is about to arrive. The list also picks up `LocalTabBarPadding`, which it was missing: rows were scrolling under the hoisted v2 tab bar, and the inset is what centers the empty state in the space the bar leaves visible. It is a no-op for v1, where the local is empty. Scoped to v2 — v1's Tips sheet keeps its bare list under the Tip Card button.
1 parent 77f885d commit c4e3571

4 files changed

Lines changed: 95 additions & 5 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!-- IconBubble3 — empty-state chat bubble (node 9340:2869). White stroke, tinted at the call site. -->
2+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:width="64dp"
4+
android:height="64dp"
5+
android:viewportWidth="64"
6+
android:viewportHeight="64">
7+
<path
8+
android:pathData="M32 56.6667C45.6229 56.6667 56.6667 45.6229 56.6667 32C56.6667 18.377 45.6229 7.33333 32 7.33333C18.377 7.33333 7.33333 18.377 7.33333 32C7.33333 35.7224 8.15792 39.2523 9.63437 42.4171C9.90619 42.9995 9.99048 43.6557 9.83867 44.2803L7.68085 53.1597C7.21389 55.0816 8.92763 56.8251 10.857 56.3912L20.1133 54.3104C20.7108 54.176 21.3342 54.2563 21.8928 54.5075C24.9774 55.8949 28.3987 56.6667 32 56.6667Z"
9+
android:strokeColor="#FFFFFF"
10+
android:strokeWidth="3"
11+
android:strokeLineCap="round"
12+
android:strokeLineJoin="round" />
13+
</vector>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,8 @@
913913
<string name="action_tips">Tips</string>
914914
<string name="title_tips">Tips</string>
915915
<string name="title_chats">Chats</string>
916+
<string name="title_noChatsYet">No Chats Yet</string>
917+
<string name="description_noChatsYet">Send a tip or share your Tip Card to start chatting</string>
916918
<string name="title_tipIntro">Receive Tips From Everyone</string>
917919
<string name="subtitle_tipIntro">Add your name to receive tips</string>
918920
<string name="action_startReceivingTips">Start Receiving Tips</string>

‎apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/TipsScreen.kt‎

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,38 @@
11
package com.flipcash.app.tipping
22

3+
import androidx.compose.foundation.Image
34
import androidx.compose.foundation.background
5+
import androidx.compose.foundation.layout.Arrangement
46
import androidx.compose.foundation.layout.Box
7+
import androidx.compose.foundation.layout.Column
58
import androidx.compose.foundation.layout.fillMaxSize
69
import androidx.compose.foundation.layout.fillMaxWidth
710
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.layout.size
812
import androidx.compose.foundation.lazy.LazyColumn
913
import androidx.compose.foundation.lazy.LazyListScope
1014
import androidx.compose.foundation.lazy.itemsIndexed
15+
import androidx.compose.material3.Text
1116
import androidx.compose.runtime.Composable
1217
import androidx.compose.runtime.getValue
1318
import androidx.compose.runtime.remember
1419
import androidx.compose.ui.Alignment
1520
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.graphics.ColorFilter
22+
import androidx.compose.ui.res.painterResource
1623
import androidx.compose.ui.res.stringResource
24+
import androidx.compose.ui.text.style.TextAlign
25+
import androidx.compose.ui.tooling.preview.Preview
26+
import androidx.compose.ui.tooling.preview.PreviewWrapper
1727
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1828
import com.flipcash.app.core.AppRoute
1929
import com.flipcash.app.core.chat.ChatIdentifier
30+
import com.flipcash.app.core.data.isLoaded
31+
import com.flipcash.app.core.navigation.LocalTabBarPadding
2032
import com.flipcash.app.core.tipping.TipStep
2133
import com.flipcash.app.featureflags.FeatureFlag
2234
import com.flipcash.app.featureflags.LocalFeatureFlags
35+
import com.flipcash.app.theme.FlipcashThemeWrapper
2336
import com.flipcash.app.tipping.internal.TipFlowViewModel
2437
import com.flipcash.app.tipping.internal.components.TipChatRow
2538
import com.flipcash.features.tipping.R
@@ -70,10 +83,14 @@ fun TipsScreen() {
7083
}
7184
}
7285
) { padding ->
86+
val chats = state.tipChats
7387
LazyColumn(
7488
modifier = Modifier
7589
.fillMaxSize()
7690
.padding(padding),
91+
// Clears the hoisted v2 tab bar (empty for v1, which has no tab bar): keeps the last row
92+
// reachable and centers the empty state in the space the bar leaves visible.
93+
contentPadding = LocalTabBarPadding.current,
7794
) {
7895
// v1 surfaces the tip card via a button here; v2 has a dedicated tip-card tab instead.
7996
if (!isNewUi) {
@@ -98,13 +115,61 @@ fun TipsScreen() {
98115
}
99116
}
100117

101-
tipChatItems(state.tipChats) { chat ->
102-
navigator.push(AppRoute.Messaging.Chat(ChatIdentifier.ByChatId(chat.chatId)))
118+
// v2 only: once the feed has loaded and there's nothing to show, the list is replaced by
119+
// a centered prompt. v1's sheet keeps its bare list under the Tip Card button.
120+
if (isNewUi && chats.isLoaded() && chats.data.isEmpty()) {
121+
item { NoChatsYet(Modifier.fillParentMaxSize()) }
122+
} else {
123+
tipChatItems(chats.dataOrNull.orEmpty()) { chat ->
124+
navigator.push(AppRoute.Messaging.Chat(ChatIdentifier.ByChatId(chat.chatId)))
125+
}
103126
}
104127
}
105128
}
106129
}
107130

131+
/**
132+
* The "Chats" tab empty state (node 9340:2746) — bubble mark, title and prompt, centered in the
133+
* space the caller gives it (the list viewport).
134+
*/
135+
@Composable
136+
private fun NoChatsYet(modifier: Modifier = Modifier) {
137+
Box(
138+
modifier = modifier,
139+
contentAlignment = Alignment.Center,
140+
) {
141+
Column(
142+
modifier = Modifier
143+
.fillMaxWidth()
144+
.padding(horizontal = CodeTheme.dimens.inset),
145+
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x3),
146+
horizontalAlignment = Alignment.CenterHorizontally,
147+
) {
148+
Image(
149+
modifier = Modifier.size(CodeTheme.dimens.grid.x16),
150+
painter = painterResource(R.drawable.ic_bubble_outline),
151+
contentDescription = null,
152+
colorFilter = ColorFilter.tint(CodeTheme.colors.textMain),
153+
)
154+
155+
Text(
156+
text = stringResource(R.string.title_noChatsYet),
157+
style = CodeTheme.typography.textLarge,
158+
color = CodeTheme.colors.textMain,
159+
textAlign = TextAlign.Center,
160+
)
161+
162+
Text(
163+
modifier = Modifier.fillMaxWidth(0.6f),
164+
text = stringResource(R.string.description_noChatsYet),
165+
style = CodeTheme.typography.textSmall,
166+
color = CodeTheme.colors.textSecondary,
167+
textAlign = TextAlign.Center,
168+
)
169+
}
170+
}
171+
}
172+
108173
private fun LazyListScope.tipChatItems(
109174
chats: List<ConversationReference>,
110175
onClick: (ConversationReference) -> Unit,
@@ -118,3 +183,10 @@ private fun LazyListScope.tipChatItems(
118183
}
119184
}
120185
}
186+
187+
@Composable
188+
@Preview
189+
@PreviewWrapper(FlipcashThemeWrapper::class)
190+
private fun PreviewNoChatsYet() {
191+
NoChatsYet(Modifier.fillMaxSize())
192+
}

‎apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/TipFlowViewModel.kt‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.flipcash.app.tipping.internal
33
import androidx.lifecycle.viewModelScope
44
import dagger.hilt.android.lifecycle.HiltViewModel
55
import com.flipcash.app.core.bill.Scannable
6+
import com.flipcash.app.core.data.Loadable
67
import com.flipcash.app.core.extensions.onResult
78
import com.flipcash.app.core.tipping.TipStep
89
import com.flipcash.app.shareable.ShareSheetController
@@ -46,14 +47,16 @@ internal class TipFlowViewModel @Inject constructor(
4647
// true when re-entering right after the user-profile setup handoff (Tips(resumed = true)).
4748
val resumed: Boolean = false,
4849
val currentStep: TipStep? = null,
49-
val tipChats: List<ConversationReference> = emptyList(),
50+
// Loading until the chat feed emits — distinguishes "still loading" from "loaded, none", so
51+
// the empty state doesn't flash on top of a list that's about to arrive.
52+
val tipChats: Loadable<List<ConversationReference>> = Loadable.Loading(),
5053
val tipCard: Scannable.TipCard? = null,
5154
)
5255

5356
sealed interface Event {
5457
data class StepsUpdated(val steps: List<TipStep>) : Event
5558
data class OnStepChanged(val step: TipStep) : Event
56-
data class ChatsUpdated(val tips: List<ConversationReference>) : Event
59+
data class ChatsUpdated(val tips: Loadable<List<ConversationReference>>) : Event
5760

5861
/** How the flow was entered — [resumed] is true for the post-setup handoff re-entry. */
5962
data class OnResumed(val resumed: Boolean) : Event
@@ -101,7 +104,7 @@ internal class TipFlowViewModel @Inject constructor(
101104
val selfId = userManager.accountId
102105
val tokensByMint = tokens.associateBy { it.address }
103106
summaries.map { it.toConversationReference(selfId, tokensByMint, resources) }
104-
}.onEach { dispatchEvent(Event.ChatsUpdated(it)) }.launchIn(viewModelScope)
107+
}.onEach { dispatchEvent(Event.ChatsUpdated(Loadable.Loaded(it))) }.launchIn(viewModelScope)
105108

106109
eventFlow
107110
.filterIsInstance<Event.ShareTipCard>()

0 commit comments

Comments
 (0)