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
53 changes: 53 additions & 0 deletions app/src/androidTest/java/to/bitkit/ui/components/SheetHostTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState
import androidx.compose.material3.SheetValue
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
Expand All @@ -24,7 +27,9 @@ import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import to.bitkit.repositories.PaykitSubscriptionId
import to.bitkit.test.annotations.ComposeUi
import to.bitkit.ui.sheets.SendRoute
import to.bitkit.ui.theme.AppThemeSurface
import kotlin.test.assertEquals

Expand Down Expand Up @@ -127,6 +132,54 @@ class SheetHostTest {
assertEquals(2, presentationCount)
}

@Test
fun subscriptionPaymentReplacementKeepsSheetExpanded() {
val sheet = mutableStateOf<Sheet>(
Sheet.Subscription(SubscriptionRoute.Review(PaykitSubscriptionId("subscription", "payee")))
)
var dismissCount = 0
var visibleCount = 0
lateinit var sheetState: SheetState
composeTestRule.setContent {
AppThemeSurface {
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
SheetHost(
shouldExpand = true,
visibilityKey = sheet.value,
onDismiss = { dismissCount++ },
onVisible = { visibleCount++ },
sheetState = sheetState,
sheets = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(320.dp)
.testTag(if (sheet.value is Sheet.Send) "SubscriptionPayment" else "SubscriptionReview")
)
},
content = { Box(Modifier.fillMaxSize()) },
)
}
}
composeTestRule.onNodeWithTag("SubscriptionReview").assertIsDisplayed()
val expandedOffset = composeTestRule.runOnIdle { sheetState.requireOffset() }
composeTestRule.mainClock.autoAdvance = false
composeTestRule.runOnIdle { sheet.value = Sheet.Send(SendRoute.Confirm) }

repeat(30) {
composeTestRule.mainClock.advanceTimeByFrame()
composeTestRule.runOnIdle {
assertEquals(SheetValue.Expanded, sheetState.currentValue)
assertEquals(SheetValue.Expanded, sheetState.targetValue)
assertEquals(expandedOffset, sheetState.requireOffset())
assertEquals(0, dismissCount)
}
}

composeTestRule.onNodeWithTag("SubscriptionPayment").assertIsDisplayed()
composeTestRule.runOnIdle { assertEquals(2, visibleCount) }
}

@Test
fun programmaticHideDoesNotInvokeDismissalCallback() {
val shouldExpand = mutableStateOf(true)
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5823,7 +5823,7 @@ class AppViewModel @Inject constructor(
val previousJob = sheetTransitionJob
val nextJob = viewModelScope.launch(start = CoroutineStart.LAZY) {
receiveSheetContext = null
_currentSheet.value?.let {
if (_currentSheet.value != null && !replacesSubscriptionReview(sheetType)) {
_currentSheet.update { null }
delay(SCREEN_TRANSITION_DELAY)
}
Expand All @@ -5846,6 +5846,13 @@ class AppViewModel @Inject constructor(
nextJob.start()
}

private fun replacesSubscriptionReview(sheet: Sheet): Boolean {
if (sheet !is Sheet.Send || !_sendUiState.value.isInitialSubscriptionPayment) return false
val review = (_currentSheet.value as? Sheet.Subscription)?.route as? SubscriptionRoute.Review ?: return false
val requestId = _sendUiState.value.incomingPaymentRequestId ?: return false
return review.id == PaykitSubscriptionId(requestId.paymentRequestId, requestId.counterparty)
}

fun hideSheet() = hideSheet(shouldFlushDeferredScan = true)

private fun hideSheet(shouldFlushDeferredScan: Boolean) {
Expand Down
80 changes: 68 additions & 12 deletions app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9308,6 +9308,7 @@ class AppViewModelSendFlowTest : BaseUnitTest() {
fun `subscription acceptance pays a due period materialized during acceptance`() = test {
val subscription = subscriptionStartingAt(Clock.System.now() + 60.seconds)
val dueRequest = paymentRequest().copy(
paymentRequestId = subscription.paymentRequestId,
lifecycleState = PaymentRequestLifecycleState.ACTIVE_RECURRING,
billingPeriod = PaykitBillingPeriod(
startsAt = Clock.System.now(),
Expand All @@ -9323,10 +9324,13 @@ class AppViewModelSendFlowTest : BaseUnitTest() {
sut.showSheet(Sheet.Subscription(SubscriptionRoute.Review(subscription.id)))
advanceUntilIdle()

val startedPayment = sut.acceptSubscriptionAndStartPayment(subscription).getOrThrow()
advanceUntilIdle()

assertTrue(startedPayment)
sut.currentSheet.test {
assertTrue(awaitItem() is Sheet.Subscription)
assertTrue(sut.acceptSubscriptionAndStartPayment(subscription).getOrThrow())
advanceUntilIdle()
assertTrue((awaitItem() as Sheet.Send).route is SendRoute.Error)
expectNoEvents()
}
verify(privatePaykitRepo).beginPaymentRequestWaitingForUpdatedList(dueRequest)
assertTrue(sut.currentSheet.value is Sheet.Send)
assertTrue(sut.sendUiState.value.isInitialSubscriptionPayment)
Expand All @@ -9335,19 +9339,71 @@ class AppViewModelSendFlowTest : BaseUnitTest() {
}

@Test
fun `initial subscription send dismisses review before presentation`() = test {
fun `initial subscription send replaces the matching review without dismissal`() = test {
val subscription = subscriptionStartingAt(Clock.System.now())
val destination = Sheet.Send(SendRoute.Confirm)
sut.showSheet(Sheet.Subscription(SubscriptionRoute.Review(subscription.id)))
val review = Sheet.Subscription(SubscriptionRoute.Review(subscription.id))
sut.showSheet(review)
advanceUntilIdle()
setSendState(SendUiState(isInitialSubscriptionPayment = true))
setSendState(
SendUiState(
isInitialSubscriptionPayment = true,
incomingPaymentRequestId = PaykitPaymentRequestId(
subscription.paymentRequestId,
subscription.counterparty,
),
)
)

sut.showSheet(destination)
runCurrent()
sut.currentSheet.test {
assertEquals(review, awaitItem())
sut.showSheet(destination)
runCurrent()

assertNull(sut.currentSheet.value)
advanceUntilIdle()
assertEquals(destination, sut.currentSheet.value)
assertEquals(destination, awaitItem())
advanceUntilIdle()
expectNoEvents()
}
}

@Test
fun `other subscription sheet transitions still dismiss before presentation`() = test {
val subscription = subscriptionStartingAt(Clock.System.now())
val review = Sheet.Subscription(SubscriptionRoute.Review(subscription.id))
val destination = Sheet.Send(SendRoute.Confirm)
val initialState = SendUiState(
isInitialSubscriptionPayment = true,
incomingPaymentRequestId = PaykitPaymentRequestId(subscription.paymentRequestId, subscription.counterparty),
)
val otherReview = Sheet.Subscription(
SubscriptionRoute.Review(subscription.id.copy(paymentRequestId = "other-subscription"))
)
val otherCounterparty = Sheet.Subscription(
SubscriptionRoute.Review(subscription.id.copy(counterparty = "other-counterparty"))
)
val details = Sheet.Subscription(SubscriptionRoute.Details(subscription.id))
listOf(
Triple(review, initialState.copy(isInitialSubscriptionPayment = false), destination),
Triple(otherReview, initialState, destination),
Triple(otherCounterparty, initialState, destination),
Triple(details, initialState, destination),
Triple(review, initialState, Sheet.Receive()),
).forEach { (source, state, target) ->
sut.showSheet(source)
advanceUntilIdle()
setSendState(state)

sut.currentSheet.test {
assertEquals(source, awaitItem())
sut.showSheet(target)
runCurrent()

assertNull(awaitItem())
advanceUntilIdle()
assertEquals(target, awaitItem())
expectNoEvents()
}
}
}

@Test
Expand Down
1 change: 1 addition & 0 deletions changelog.d/next/1469.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Kept the subscription review sheet open when starting the first payment.
3 changes: 2 additions & 1 deletion journeys/subscriptions/review-and-subscribe.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
<action>Verify the Review and Subscribe sheet appears with 5,000 sats and the creator contact card</action>
<action>Verify the swipe control reads "Swipe To Subscribe &amp; Pay" when the first period is due on acceptance, otherwise "Swipe To Subscribe"</action>
<action>Swipe the confirm control to the end</action>
<action>If the first period was due on acceptance: complete the send flow that opens, then verify the Subscribed confirmation appears</action>
<action>If the first period was due on acceptance: verify the sheet stays expanded as Review and Subscribe changes to the send flow, without closing, exposing Home or the subscriptions list, and sliding back in</action>
<action>If the first period was due on acceptance: complete the send flow inside the same sheet, then verify the Subscribed confirmation appears</action>
<action>If no payment was due: verify the Subscribed confirmation appears directly, with no send flow</action>
<action>Tap Close on the Subscribed confirmation</action>
<action>If a payment was made: verify its billing period is no longer offered with Pay or Dismiss, no Payment Requests sheet opens for it, and the home payment-request bell is absent when no other requests are pending</action>
Expand Down
Loading