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 @@ -2,6 +2,7 @@ package com.flipcash.app.core.send

import android.os.Parcelable
import com.getcode.navigation.flow.FlowStep
import com.getcode.opencode.model.financial.Fiat
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable

Expand All @@ -22,7 +23,18 @@ sealed interface SendStep : FlowStep, Parcelable {
@Parcelize
@Serializable
data object ContactList : SendStep

@Parcelize
@Serializable
data class AmountEntry(
val e164: String,
val displayName: String,
) : SendStep
}

@Serializable
sealed interface SendResult : Parcelable
sealed interface SendResult : Parcelable {
@Parcelize
@Serializable
data class Sent(val amount: Fiat) : SendResult
}
13 changes: 13 additions & 0 deletions apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
<string name="subtitle_giveCashHint">Enter up to %1$s</string>
<string name="subtitle_giveCashHintLimitExceeded">You can only give up to %1$s</string>

<string name="subtitle_sendHint">Enter up to %1$s</string>
<string name="subtitle_sendHintLimitExceeded">You can only send up to %1$s</string>

<string name="error_title_sendLimitReached">Transaction Limit Reached</string>
<string name="error_description_sendLimitReached">Flipcash is designed for small, every day transactions. Send limits reset daily</string>

Expand Down Expand Up @@ -731,4 +734,14 @@
<item quantity="other">%1$s Contacts Already On Flipcash</item>
</plurals>
<string name="prompt_description_contactsAlreadyOnFlipcash">Send them money, or invite other contacts to sign up for Flipcash</string>

<string name="error_title_contactNotOnFlipcash">Something Went Wrong</string>
<string name="error_description_contactNotOnFlipcash">This contact isn\'t on Flipcash. Pick someone else to send cash</string>

<string name="prompt_title_fundsSentToContact">Cash Sent Successfully</string>
<string name="prompt_description_fundsSentToContact">%1$s is on its way to %2$s</string>

<string name="error_title_cashFailedToSend">Something Went Wrong</string>
<string name="error_description_cashFailedToSend">We were unable to send your cash. Please try again</string>
<string name="action_swipeToSend">Swipe to Send</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,43 @@ package com.flipcash.app.directsend
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import com.flipcash.app.contacts.device.DeviceContact
import com.flipcash.app.core.AppRoute
import com.flipcash.app.core.send.SendResult
import com.flipcash.app.core.send.SendStep
import com.flipcash.app.core.tokens.TokenPurpose
import com.flipcash.app.core.ui.ConfirmationStyle
import com.flipcash.app.core.ui.TokenSelectionPill
import com.flipcash.app.directsend.internal.SendFlowViewModel
import com.flipcash.app.directsend.internal.screens.AmountEntryResult
import com.flipcash.app.directsend.internal.screens.AmountEntryScreen
import com.flipcash.app.directsend.internal.screens.ContactListScreen
import com.flipcash.app.directsend.internal.screens.ContactsPermissionGateScreen
import com.flipcash.app.directsend.internal.screens.PhoneGateLandingScreen
import com.flipcash.features.directsend.R
import com.getcode.manager.BottomBarManager
import com.getcode.navigation.annotatedEntry
import com.getcode.navigation.flow.FlowExitReason
import com.getcode.navigation.flow.FlowHost
import com.getcode.navigation.results.NavResultStateRegistry
import com.getcode.navigation.flow.LocalOuterCodeNavigator
import com.getcode.navigation.flow.flowSharedViewModel
import com.getcode.navigation.flow.rememberFlowNavigator
import com.getcode.navigation.results.NavResultStateRegistry
import com.getcode.navigation.scenes.LocalBottomSheetDismissDispatcher
import com.getcode.opencode.model.financial.Fiat
import com.getcode.solana.keys.PublicKey
import com.getcode.util.resources.LocalResources
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

@Composable
fun SendFlowScreen(resultStateRegistry: NavResultStateRegistry) {
Expand All @@ -42,19 +62,107 @@ fun SendFlowScreen(resultStateRegistry: NavResultStateRegistry) {
)
}

private fun sendEntryProvider(): (NavKey) -> NavEntry<NavKey> = entryProvider {
annotatedEntry<SendStep.PhoneGate> {
SyncStep(it)
PhoneGateLandingScreen()
@Composable
private fun sendEntryProvider(): (NavKey) -> NavEntry<NavKey> {
return entryProvider {
annotatedEntry<SendStep.PhoneGate> {
SyncStep(it)
PhoneGateLandingScreen()
}
annotatedEntry<SendStep.ContactsGate> {
SyncStep(it)
ContactsPermissionGateScreen()
}
annotatedEntry<SendStep.ContactList> {
SyncStep(it)
ContactListScreen()
}
annotatedEntry<SendStep.AmountEntry> { step ->
SyncStep(step)
SendAmountEntryScreen()
}
}
annotatedEntry<SendStep.ContactsGate> {
SyncStep(it)
ContactsPermissionGateScreen()
}

@Composable
private fun SendAmountEntryScreen() {
val flowNavigator = rememberFlowNavigator<SendStep, SendResult>()
val sharedVm = flowSharedViewModel<SendFlowViewModel>()
val sharedState by sharedVm.stateFlow.collectAsStateWithLifecycle()
val resources = LocalResources.current

var contact by remember {
mutableStateOf<DeviceContact?>(null)
}
annotatedEntry<SendStep.ContactList> {
SyncStep(it)
ContactListScreen()

var resolvedAuthority by remember {
mutableStateOf<PublicKey?>(null)
}

LaunchedEffect(Unit) {
sharedVm.eventFlow
.filterIsInstance<SendFlowViewModel.Event.ResolveCompleted>()
.onEach {
contact = it.contact
resolvedAuthority = it.authority
}
.launchIn(this)
}

LaunchedEffect(sharedVm) {
sharedVm.eventFlow
.filterIsInstance<SendFlowViewModel.Event.SendComplete>()
.onEach { event ->
BottomBarManager.showInfo(
title = resources.getString(R.string.prompt_title_fundsSentToContact),
message = resources.getString(
R.string.prompt_description_fundsSentToContact,
event.amount.formatted(rule = Fiat.FormattingRule.Truncated),
contact?.displayName ?: "your selected recipient"
),
onDismiss = { flowNavigator.back() }
)
}.launchIn(this)
}

AmountEntryScreen(
title = { token ->
TokenSelectionPill(token) {
flowNavigator.navigate(
AppRoute.Sheets.TokenSelection(TokenPurpose.Select)
)
}
},
// region lives outside the flow
// flow replaces LocalCodeNavigator for the flow as the "inner" navigator
navigator = LocalOuterCodeNavigator.current,
canChangeCurrency = true,
confirmationStyle = ConfirmationStyle.Slide,
confirmationState = sharedState.sendProgress,
onResult = { result ->
when (result) {
AmountEntryResult.Cancelled -> flowNavigator.back()
is AmountEntryResult.Confirmed -> {
val destination = resolvedAuthority
if (destination == null) {
BottomBarManager.showAlert(
title = resources.getString(R.string.error_title_contactNotOnFlipcash),
message = resources.getString(R.string.error_description_contactNotOnFlipcash),
onDismiss = { flowNavigator.back() }
)
} else {
sharedVm.dispatchEvent(
SendFlowViewModel.Event.OnSendRequested(
amount = result.amount,
token = result.token,
destinationOwner = destination,
)
)
}
}
}
},
)
}

@Composable
Expand Down
Loading
Loading