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
13 changes: 13 additions & 0 deletions apps/flipcash/core/src/main/res/drawable/ic_bubble_outline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- IconBubble3 — empty-state chat bubble (node 9340:2869). White stroke, tinted at the call site. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
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"
android:strokeColor="#FFFFFF"
android:strokeWidth="3"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>
2 changes: 2 additions & 0 deletions apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,8 @@
<string name="action_tips">Tips</string>
<string name="title_tips">Tips</string>
<string name="title_chats">Chats</string>
<string name="title_noChatsYet">No Chats Yet</string>
<string name="description_noChatsYet">Send a tip or share your Tip Card to start chatting</string>
<string name="title_tipIntro">Receive Tips From Everyone</string>
<string name="subtitle_tipIntro">Add your name to receive tips</string>
<string name="action_startReceivingTips">Start Receiving Tips</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
package com.flipcash.app.tipping

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewWrapper
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flipcash.app.core.AppRoute
import com.flipcash.app.core.chat.ChatIdentifier
import com.flipcash.app.core.data.isLoaded
import com.flipcash.app.core.navigation.LocalTabBarPadding
import com.flipcash.app.core.tipping.TipStep
import com.flipcash.app.featureflags.FeatureFlag
import com.flipcash.app.featureflags.LocalFeatureFlags
import com.flipcash.app.theme.FlipcashThemeWrapper
import com.flipcash.app.tipping.internal.TipFlowViewModel
import com.flipcash.app.tipping.internal.components.TipChatRow
import com.flipcash.features.tipping.R
Expand Down Expand Up @@ -70,10 +83,14 @@ fun TipsScreen() {
}
}
) { padding ->
val chats = state.tipChats
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(padding),
// Clears the hoisted v2 tab bar (empty for v1, which has no tab bar): keeps the last row
// reachable and centers the empty state in the space the bar leaves visible.
contentPadding = LocalTabBarPadding.current,
) {
// v1 surfaces the tip card via a button here; v2 has a dedicated tip-card tab instead.
if (!isNewUi) {
Expand All @@ -98,13 +115,61 @@ fun TipsScreen() {
}
}

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

/**
* The "Chats" tab empty state (node 9340:2746) — bubble mark, title and prompt, centered in the
* space the caller gives it (the list viewport).
*/
@Composable
private fun NoChatsYet(modifier: Modifier = Modifier) {
Box(
modifier = modifier,
contentAlignment = Alignment.Center,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = CodeTheme.dimens.inset),
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x3),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
modifier = Modifier.size(CodeTheme.dimens.grid.x16),
painter = painterResource(R.drawable.ic_bubble_outline),
contentDescription = null,
colorFilter = ColorFilter.tint(CodeTheme.colors.textMain),
)

Text(
text = stringResource(R.string.title_noChatsYet),
style = CodeTheme.typography.textLarge,
color = CodeTheme.colors.textMain,
textAlign = TextAlign.Center,
)

Text(
modifier = Modifier.fillMaxWidth(0.6f),
text = stringResource(R.string.description_noChatsYet),
style = CodeTheme.typography.textSmall,
color = CodeTheme.colors.textSecondary,
textAlign = TextAlign.Center,
)
}
}
}

private fun LazyListScope.tipChatItems(
chats: List<ConversationReference>,
onClick: (ConversationReference) -> Unit,
Expand All @@ -118,3 +183,10 @@ private fun LazyListScope.tipChatItems(
}
}
}

@Composable
@Preview
@PreviewWrapper(FlipcashThemeWrapper::class)
private fun PreviewNoChatsYet() {
NoChatsYet(Modifier.fillMaxSize())
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.flipcash.app.tipping.internal
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import com.flipcash.app.core.bill.Scannable
import com.flipcash.app.core.data.Loadable
import com.flipcash.app.core.extensions.onResult
import com.flipcash.app.core.tipping.TipStep
import com.flipcash.app.shareable.ShareSheetController
Expand Down Expand Up @@ -46,14 +47,16 @@ internal class TipFlowViewModel @Inject constructor(
// true when re-entering right after the user-profile setup handoff (Tips(resumed = true)).
val resumed: Boolean = false,
val currentStep: TipStep? = null,
val tipChats: List<ConversationReference> = emptyList(),
// Loading until the chat feed emits — distinguishes "still loading" from "loaded, none", so
// the empty state doesn't flash on top of a list that's about to arrive.
val tipChats: Loadable<List<ConversationReference>> = Loadable.Loading(),
val tipCard: Scannable.TipCard? = null,
)

sealed interface Event {
data class StepsUpdated(val steps: List<TipStep>) : Event
data class OnStepChanged(val step: TipStep) : Event
data class ChatsUpdated(val tips: List<ConversationReference>) : Event
data class ChatsUpdated(val tips: Loadable<List<ConversationReference>>) : Event

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

eventFlow
.filterIsInstance<Event.ShareTipCard>()
Expand Down
Loading