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 @@ -11,18 +11,23 @@ import com.getcode.opencode.model.financial.Token
import kotlin.time.Duration

data class BillState(
val bill: Scannable.Payable?,
val bill: Scannable?,
val showToast: Boolean,
val toast: BillToast?,
val valuation: Valuation?,
val primaryAction: Action?,
val secondaryAction: Action?,
) {
val canSwipeToDismiss: Boolean
get() = bill?.canSwipeToDismiss == true
get() = when (val b = bill) {
null -> false
is Scannable.Payable -> b.canSwipeToDismiss
else -> true // non-payment scannables (e.g. TipCard) are dismissable
}

val confirmationDelayMillis: Int
get() = (bill?.confirmationDelay ?: Duration.ZERO).inWholeMilliseconds.toInt()
get() = ((bill as? Scannable.Payable)?.confirmationDelay ?: Duration.ZERO)
.inWholeMilliseconds.toInt()

companion object {
val Default = BillState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,9 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flipcash.app.bill.customization.components.BillPlayground
import com.flipcash.app.bills.AnimatedBill
import com.flipcash.app.core.bill.Scannable
import com.flipcash.app.bills.AnimatedScannable
import com.flipcash.features.bill.playground.R
import com.getcode.theme.CodeTheme
import com.getcode.ui.components.AppBarDefaults
import com.getcode.ui.core.measured
import androidx.compose.foundation.clickable
import com.getcode.ui.utils.AnimationUtils
Expand Down Expand Up @@ -104,7 +102,7 @@ fun BillPlaygroundScaffold(content: @Composable () -> Unit) {

Box(modifier = Modifier.fillMaxSize()) {
content()
AnimatedBill(
AnimatedScannable(
modifier = Modifier.fillMaxSize(),
dismissState = billDismissState,
dismissed = !isUsingPlayground,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import com.flipcash.app.bill.customization.Event
import com.flipcash.app.bill.customization.LocalBillPlaygroundController
import com.flipcash.app.bill.customization.PlaygroundContext
import com.flipcash.app.bill.customization.components.BillPlayground
import com.flipcash.app.bills.RenderedBill
import com.flipcash.app.core.bill.Scannable
import com.flipcash.app.bills.ScannableRenderer
import com.flipcash.app.core.ui.transitions.SharedTransition
import com.flipcash.app.core.ui.transitions.sharedBoundsTransition
import com.flipcash.app.currencycreator.internal.CurrencyCreatorViewModel
Expand Down Expand Up @@ -88,15 +87,15 @@ internal fun BillCustomizationContent(
horizontalAlignment = Alignment.CenterHorizontally
) {
augmentedBill?.let { bill ->
RenderedBill(
ScannableRenderer(
modifier = Modifier
.padding(top = CodeTheme.dimens.grid.x3)
.fillMaxWidth()
.weight(1f)
.sharedBoundsTransition(
transition = SharedTransition.CurrencyBill
),
bill = bill,
scannable = bill,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flipcash.app.bills.RenderedBill
import com.flipcash.app.bills.ScannableRenderer
import com.flipcash.app.core.tokens.CurrencyCreatorResult
import com.flipcash.app.core.tokens.CurrencyCreatorStep
import com.flipcash.app.core.ui.TokenIconWithName
Expand All @@ -32,7 +32,6 @@ import com.getcode.ui.theme.CodeButton
import com.getcode.ui.theme.CodeScaffold
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach

@Composable
Expand Down Expand Up @@ -120,14 +119,14 @@ internal fun ReviewContent(
}

state.bill?.let { bill ->
RenderedBill(
ScannableRenderer(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.sharedBoundsTransition(
transition = SharedTransition.CurrencyBill
),
bill = bill,
scannable = bill,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import com.flipcash.app.core.extensions.navigateAll
import com.flipcash.app.core.extensions.openAsSheet
import com.flipcash.app.core.navigation.DeeplinkType
import com.flipcash.app.router.LocalRouter
import com.flipcash.app.scanner.internal.bills.BillContainer
import com.flipcash.app.scanner.internal.bills.ScannableContainer
import com.flipcash.app.session.LocalSessionController
import com.getcode.libs.code.detection.CodeScanResult
import com.getcode.navigation.core.LocalCodeNavigator
Expand Down Expand Up @@ -74,7 +74,7 @@ internal fun Scanner() {
}

@SuppressLint("LocalContextGetResourceValueCall")
BillContainer(
ScannableContainer(
isPaused = isPaused,
isPinching = isPinching,
zoomRatio = zoomRatio,
Expand All @@ -90,7 +90,7 @@ internal fun Scanner() {
navigator.openAsSheet(route)
}

return@BillContainer
return@ScannableContainer
}
}
else -> Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flipcash.app.bills.AnimatedBill
import com.flipcash.app.bills.AnimatedScannable
import com.flipcash.app.core.android.extensions.launchAppSettings
import com.flipcash.app.core.bill.Scannable
import com.flipcash.app.scanner.internal.ScannerDecorItem
import com.flipcash.app.scanner.internal.ui.components.DecorView
import com.flipcash.app.scanner.internal.ui.modals.ReceivedFundsConfirmation
Expand All @@ -59,10 +59,11 @@ import com.getcode.ui.utils.AnimationUtils
import com.getcode.util.permissions.PermissionResult
import com.getcode.util.permissions.rememberCameraPermission
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.milliseconds

@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun BillContainer(
internal fun ScannableContainer(
modifier: Modifier = Modifier,
isPaused: Boolean,
isPinching: Boolean = false,
Expand Down Expand Up @@ -183,7 +184,7 @@ internal fun BillContainer(

LaunchedEffect(dismissed) {
if (dismissed) {
delay(500)
delay(500.milliseconds)
dismissed = false
}
}
Expand Down Expand Up @@ -216,7 +217,7 @@ internal fun BillContainer(
}
}

AnimatedBill(
AnimatedScannable(
modifier = Modifier.fillMaxSize(),
dismissState = billDismissState,
dismissed = dismissed,
Expand All @@ -240,58 +241,84 @@ internal fun BillContainer(
}
)

//Bill management options
AnimatedVisibility(
modifier = Modifier
.align(BottomCenter)
.measured { managementHeight = it.height },
visible = showManagementOptions,
enter = fadeIn(),
exit = fadeOut(tween(100)),
) {
var canCancel by remember {
mutableStateOf(false)
}
BillManagementOptions(
modifier = Modifier
.windowInsetsPadding(WindowInsets.navigationBars),
primaryAction = updatedBillState.primaryAction,
secondaryAction = updatedBillState.secondaryAction,
isSending = updatedState.isRemoteSendLoading,
isInteractable = canCancel,
)

LaunchedEffect(transition.isRunning, transition.targetState) {
// wait for spring settle to enable cancel to not prematurely cancel
// the enter. doing so causing the exit of the bill to not run, or run its own dismiss animation
if (transition.targetState == EnterExitState.Visible && transition.currentState == transition.targetState) {
delay(500)
canCancel = true
}
}

BackHandler(canCancel) {
session.dismissBill(PutInWallet)
}
// Below-bill content, folded by scannable type. `displayedScannable` retains the
// last shown scannable so an arm's modal can still animate OUT as `bill` returns to
// null on dismiss (the arm stays mounted; only `visible` flips).
var displayedScannable by remember { mutableStateOf<Scannable?>(null) }
LaunchedEffect(updatedBillState.bill) {
updatedBillState.bill?.let { displayedScannable = it }
}

//Bill Received Bottom Dialog
AnimatedVisibility(
modifier = Modifier.align(BottomCenter),
visible = updatedBillState.bill?.didReceive ?: false,
enter = AnimationUtils.modalEnter(billState.confirmationDelayMillis),
exit = AnimationUtils.modalExit,
) {
if (updatedBillState.bill != null) {
Box(
contentAlignment = BottomCenter
when (val shown = displayedScannable) {
is Scannable.Payable -> {
//Bill management options
AnimatedVisibility(
modifier = Modifier
.align(BottomCenter)
.measured { managementHeight = it.height },
visible = updatedBillState.bill is Scannable.Payable && showManagementOptions,
enter = fadeIn(),
exit = fadeOut(tween(100)),
) {
ReceivedFundsConfirmation(
bill = updatedBillState.bill!!,
onClaim = { session.dismissBill(PutInWallet) }
var canCancel by remember {
mutableStateOf(false)
}
BillManagementOptions(
modifier = Modifier
.windowInsetsPadding(WindowInsets.navigationBars),
primaryAction = updatedBillState.primaryAction,
secondaryAction = updatedBillState.secondaryAction,
isSending = updatedState.isRemoteSendLoading,
isInteractable = canCancel,
)

LaunchedEffect(transition.isRunning, transition.targetState) {
// wait for spring settle to enable cancel to not prematurely cancel
// the enter. doing so causing the exit of the bill to not run, or run its own dismiss animation
if (transition.targetState == EnterExitState.Visible && transition.currentState == transition.targetState) {
delay(500)
canCancel = true
}
}

BackHandler(canCancel) {
session.dismissBill(PutInWallet)
}
}

//Bill Received Bottom Dialog
AnimatedVisibility(
modifier = Modifier.align(BottomCenter),
visible = (updatedBillState.bill as? Scannable.Payable)?.didReceive == true,
enter = AnimationUtils.modalEnter(billState.confirmationDelayMillis),
exit = AnimationUtils.modalExit,
) {
Box(
contentAlignment = BottomCenter
) {
ReceivedFundsConfirmation(
bill = shown,
onClaim = { session.dismissBill(PutInWallet) }
)
}
}
}

is Scannable.TipCard -> {
// TODO(owner): the tip card's bottom modal (analogous to ReceivedFundsConfirmation).
// Gate `visible` on the live bill so it animates out on dismiss, and use `shown`
// (the retained Scannable.TipCard) for content so it persists through the exit:
// AnimatedVisibility(
// modifier = Modifier.align(BottomCenter),
// visible = updatedBillState.bill is Scannable.TipCard,
// enter = AnimationUtils.modalEnter(0),
// exit = AnimationUtils.modalExit,
// ) {
// TipCardModal(tipCard = shown, onDone = { session.dismissBill(PutInWallet) })
// }
}

null -> Unit
}
}
}
1 change: 1 addition & 0 deletions apps/flipcash/shared/bills/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
implementation(libs.firebase.messaging)

implementation(project(":libs:messaging"))
implementation(project(":apps:flipcash:shared:common-ui"))

implementation(libs.androidx.datastore)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ import com.getcode.ui.theme.CustomSwipeToDismiss

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AnimatedBill(
fun AnimatedScannable(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(
horizontal = CodeTheme.dimens.inset,
vertical = CodeTheme.dimens.grid.x2
),
dismissState: DismissState,
dismissed: Boolean,
transitionSpec: AnimatedContentTransitionScope<Scannable.Payable?>.() -> ContentTransform,
contentKey: (Scannable.Payable?) -> Any? = { it },
bill: Scannable.Payable?,
transitionSpec: AnimatedContentTransitionScope<Scannable?>.() -> ContentTransform,
contentKey: (Scannable?) -> Any? = { it },
bill: Scannable?,
) {
AnimatedContent(
modifier = modifier,
Expand All @@ -47,16 +47,16 @@ fun AnimatedBill(
state = dismissState,
dismissContent = {
if (b != null && !dismissed) {
RenderedBill(
ScannableRenderer(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(contentPadding),
bill = b
scannable = b
)
}
},
directions = if (b?.disableGestures ?: false) {
directions = if ((b as? Scannable.Payable)?.disableGestures == true) {
emptySet()
} else {
setOf(DismissDirection.EndToStart, DismissDirection.StartToEnd)
Expand Down
Loading
Loading