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 @@ -319,7 +319,11 @@ private fun AppToastHost(
val gap = CodeTheme.dimens.grid.x2
val systemBottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
val bottom by animateDpAsState(
targetValue = if (barVisibility.isVisible) barHeight() + gap else systemBottom + CodeTheme.dimens.grid.x3,
targetValue = maxOf(
if (barVisibility.isVisible) barHeight() + gap else systemBottom + CodeTheme.dimens.grid.x3,
// A screen with a button pinned to the bottom asks for the toast to rest above it.
hostState.bottomClearance.let { if (it > 0.dp) it + gap else 0.dp },
),
label = "toastBottom",
)
FloatingToastHost(
Expand Down
8 changes: 8 additions & 0 deletions apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,7 @@
options name the state they produce ("1 Week") rather than the act ("Mute for 1 week"),
which is also what lets "Never" sit among them. -->
<string name="title_muteChat">Mute Notifications</string>
<string name="title_unmuteChat">Unmute Notifications</string>
<!-- Load-bearing rather than decorative: a muted chat still delivers the push, still stores the
message and still counts it unread. Only the notification is held back. -->
<string name="subtitle_muteChat">You\'ll still receive messages and see unread counts. Only notifications are silenced</string>
Expand Down Expand Up @@ -1281,6 +1282,13 @@
<string name="prompt_title_blockUser">Block %1$s?</string>
<string name="prompt_description_blockUser">You won’t see messages from them, but you will still receive cash they send you. Flipcash won’t tell them you blocked them</string>
<string name="action_unblock">Unblock</string>
<!-- The chip under a blocked person's handle on their profile. -->
<string name="label_blocked">Blocked</string>
<!-- The pinned button on another person's profile when no start-chatting fee is known. -->
<string name="action_startChatting">Start Chatting</string>
<string name="action_openChat">Open Chat</string>
<!-- The top bar's overflow button on another person's profile. -->
<string name="action_moreProfileActions">More</string>
<string name="prompt_title_unblockUser">Unblock %1$s?</string>
<string name="prompt_description_unblockUser">The conversation with them will reappear in Tips</string>
<string name="title_blocklistEmpty">No One Blocked</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.flipcash.services.user.UserManager
import com.flipcash.shared.payments.TipPaymentDelegate
import com.flipcash.shared.tipping.TippingCoordinator
import com.flipcash.libs.coroutines.DispatcherProvider
import com.flipcash.shared.common.ui.profile.joinedLabel
import com.getcode.manager.BottomBarAction
import com.getcode.manager.BottomBarManager
import com.getcode.opencode.model.financial.Fiat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,11 @@ private fun FlowInitPaymentScreen() {
fee = state.chatInitFee,
token = state.token,
sendProgress = state.sendProgress,
eventFlow = viewModel.eventFlow,
onConfirm = { viewModel.dispatchEvent(ChatViewModel.Event.OnInitPaymentConfirmed) },
onSendComplete = { resultBack.returnValue(ChatSendResult) },
sendComplete = remember(viewModel) {
viewModel.eventFlow.filterIsInstance<ChatViewModel.Event.SendComplete>().map { }
},
)
}

Expand Down Expand Up @@ -313,13 +315,26 @@ private fun FlowGroupInviteSheet() {
@Composable
private fun FlowChatProfileScreen(participant: ChatParticipant, origin: ProfileOrigin) {
val viewModel = flowSharedViewModel<ChatProfileViewModel>()
val chatViewModel = flowSharedViewModel<ChatViewModel>()
val flowNavigator = rememberFlowNavigator<ChatStep, Parcelable>()
val rootNavigator = LocalCodeNavigator.current

LaunchedEffect(viewModel, participant) {
viewModel.dispatchEvent(ChatProfileViewModel.Event.OnParticipantSet(participant))
}

ChatProfileScreen(viewModel, flowSharedViewModel<ChatViewModel>(), origin)
ChatProfileScreen(
viewModel = viewModel,
onOpenChat = { chatId ->
val chatState = chatViewModel.stateFlow.value
if (origin == ProfileOrigin.Chat && chatState.chatId == chatId) {
// The chat is the one underneath: back to it rather than a second copy.
flowNavigator.back()
} else {
rootNavigator.push(AppRoute.Messaging.Chat(ChatIdentifier.ByChatId(chatId)))
}
},
)

LaunchedEffect(viewModel) {
viewModel.eventFlow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flipcash.app.core.AppRoute
import com.flipcash.app.core.chat.ChatIdentifier
import com.flipcash.app.core.chat.ProfileAddress
import com.flipcash.app.core.extensions.navigateAll
import com.flipcash.app.messenger.internal.screens.profile.ChatProfileViewModel
Expand Down Expand Up @@ -62,7 +63,7 @@ fun ProfileScreen(address: ProfileAddress) {
LaunchedEffect(viewModel, state.participant) {
state.participant?.let {
viewModel.dispatchEvent(
ChatProfileViewModel.Event.OnParticipantSet(it, fromServer = true)
ChatProfileViewModel.Event.OnParticipantSet(it, isFullProfile = true)
)
}
}
Expand Down Expand Up @@ -97,9 +98,11 @@ fun ProfileScreen(address: ProfileAddress) {
} else {
PersonProfileScreen(
viewModel = viewModel,
chat = null,
cashSymbol = state.cashSymbol,
onBack = { navigator.pop() },
// Reached by link, so there is no chat underneath to return to.
onOpenChat = { chatId ->
navigator.push(AppRoute.Messaging.Chat(ChatIdentifier.ByChatId(chatId)))
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.flatMap
import androidx.paging.map
import com.flipcash.analytics.AddMoneySource
import com.flipcash.analytics.CashLinkChoice
import com.flipcash.analytics.GroupAccess as AnalyticsGroupAccess
import com.flipcash.analytics.GroupGateFunding
import com.flipcash.analytics.GroupInviteMethod
import com.flipcash.analytics.GroupInviteSheetSource
import com.flipcash.analytics.GroupInviteSource
import com.flipcash.analytics.State as AnalyticsState
import com.flipcash.analytics.events.AddMoneyEvents
import com.flipcash.analytics.events.ChatEvents
import com.flipcash.analytics.events.GroupEvents
import com.flipcash.analytics.events.TransferEvents
Expand All @@ -41,7 +39,7 @@ import com.flipcash.app.core.tokens.brandedName
import com.flipcash.app.core.tokens.isReserve
import com.flipcash.app.core.ui.ConfirmationStyle
import com.flipcash.app.core.util.Linkify
import com.flipcash.app.funding.PurchaseMethodController
import com.flipcash.app.messenger.internal.payment.StartChattingPayer
import com.flipcash.app.messenger.internal.link.CashCardTap
import com.flipcash.app.messenger.internal.link.ClaimReplyTargets
import com.flipcash.app.messenger.internal.link.LinkCardClassifier
Expand Down Expand Up @@ -218,7 +216,7 @@ internal class ChatViewModel @Inject constructor(
private val tokenCoordinator: TokenCoordinator,
private val exchange: Exchange,
private val verifiedFiatCalculator: VerifiedFiatCalculator,
private val purchaseMethodController: PurchaseMethodController,
private val startChattingPayer: StartChattingPayer,
private val userManager: UserManager,
private val resources: ResourceHelper,
private val analytics: FlipcashAnalytics,
Expand Down Expand Up @@ -2619,6 +2617,8 @@ internal class ChatViewModel @Inject constructor(
.onEach { onConfirmRequested() }
.launchIn(viewModelScope)

// Duplicates StartChattingPayer.pay (minus its send-limit check); this fee-payment path goes
// away with the paid-DM gate in profile-refresh slice 5.
eventFlow.filterIsInstance<Event.OnInitPaymentConfirmed>()
.onEach { onConfirmRequested(fixedAmount = minAmountFlow.value) }
.launchIn(viewModelScope)
Expand All @@ -2629,12 +2629,11 @@ internal class ChatViewModel @Inject constructor(
// Event.OnSendRequested).
.filter { stateFlow.value.participant != null || stateFlow.value.chatType == ChatType.GROUP }
.onEach {
if (!tokenCoordinator.hasGiveableBalance()) {
if (!tokenCoordinator.hasBalance()) {
presentAddMoney()
} else {
presentDiscoverCurrencies()
}
if (!startChattingPayer.mayProceed(
onAddMoney = { dispatchEvent(Event.PresentDepositOptions) },
onDiscoverCurrencies = { dispatchEvent(Event.OpenScreen(AppRoute.Token.Discovery, asSheet = true)) },
)
) {
return@onEach
}
// The payment that opens a tip DM costs exactly the recipient's fee, so there is
Expand All @@ -2653,8 +2652,7 @@ internal class ChatViewModel @Inject constructor(
eventFlow
.filterIsInstance<Event.PresentDepositOptions>()
.onEach {
analytics.track(AddMoneyEvents.opened(AddMoneySource.CHAT))
purchaseMethodController.presentDepositOptions()?.let { route ->
startChattingPayer.depositRoute()?.let { route ->
dispatchEvent(Event.OpenScreen(route))
}
}.launchIn(viewModelScope)
Expand Down Expand Up @@ -2889,7 +2887,9 @@ internal class ChatViewModel @Inject constructor(
val balanceInLocal = balance.convertingTo(rate)
val isOverBalance = amount.valueGreaterThan(balanceInLocal)
if (isOverBalance) {
presentInsufficientBalance()
startChattingPayer.presentInsufficientBalance(
onAddMoney = { dispatchEvent(Event.PresentDepositOptions) },
)
}
return isOverBalance
}
Expand Down Expand Up @@ -2930,56 +2930,6 @@ internal class ChatViewModel @Inject constructor(
}
}

/**
* Over balance, with something in the account: the same prompt the tip card raises, offering
* the way out of it. [presentAddMoney] covers the empty account, which has nothing to enter a
* smaller amount than.
*/
private fun presentInsufficientBalance() {
BottomBarManager.showInfo(
title = resources.getString(R.string.title_insufficientBalance),
message = resources.getString(R.string.description_insufficientBalanceToUse),
actions = listOf(
BottomBarAction(
text = resources.getString(R.string.action_addMoney)
) {
dispatchEvent(Event.PresentDepositOptions)
},
),
showCancel = true,
)
}

private fun presentAddMoney() {
BottomBarManager.showInfo(
title = resources.getString(R.string.title_noBalanceYet),
message = resources.getString(R.string.description_noBalanceYetToSend),
actions = listOf(
BottomBarAction(
text = resources.getString(R.string.action_addMoney)
) {
dispatchEvent(Event.PresentDepositOptions)
},
),
showCancel = true,
)
}

private fun presentDiscoverCurrencies() {
BottomBarManager.showInfo(
title = resources.getString(R.string.title_noCommunityCurrenciesYet),
message = resources.getString(R.string.description_noCommunityCurrenciesYet),
actions = listOf(
BottomBarAction(
text = resources.getString(R.string.action_discoverCurrencies)
) {
dispatchEvent(Event.OpenScreen(AppRoute.Token.Discovery, asSheet = true))
},
),
showCancel = true,
)
}

companion object {
/**
* How often a visible claimable voucher is re-asked about.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.flipcash.app.messenger.internal.payment

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent

@Module
@InstallIn(ViewModelComponent::class)
internal abstract class StartChattingModule {
@Binds
abstract fun bindStartChattingPayer(impl: DefaultStartChattingPayer): StartChattingPayer
}
Loading
Loading