Skip to content

Limited-time offer bottom sheet for Jetpack Compose

LimitedOfferSheet is design 5 of M3 Expressive Paywall. A ModalBottomSheet you can show over any screen: a big discount, the original price struck through, a countdown and a wavy progress bar that drains as time runs out. When the time is up, the button switches to “Offer ended” and is disabled.

Limited-time offer bottom sheet, light theme
Light
Limited-time offer bottom sheet, dark theme
Dark
implementation("com.github.sanikantv:m3-expressive-paywall:v1.0.0")
  • Win-back offers for lapsed subscribers.
  • A launch or seasonal discount with a real deadline.
// LimitedOfferSheet takes a material3 SheetState, which material3 marks experimental.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OfferScreen(buy: (PaywallPlan) -> Unit, restore: () -> Unit) {
var showOffer by rememberSaveable { mutableStateOf(true) }
// Keep the deadline stable. In a real app, store it (e.g. in DataStore) so it survives restarts.
val endsAt = remember { System.currentTimeMillis() + 15 * 60_000L }
if (showOffer) {
LimitedOfferSheet(
plan = Sample.yearly.copy(
price = "$14.99",
trialText = null,
priceDetail = "First year only, then $29.99 per year",
),
originalPrice = "$29.99",
discountLabel = "-50%",
offerEndsAtMillis = endsAt,
offerDurationMillis = 15 * 60_000L,
legal = Sample.legal,
onPurchase = buy,
onRestore = restore,
onClose = { showOffer = false },
)
}
}
Parameter Type Default Description
plan PaywallPlan required The discounted plan. Its price is the offer price.
originalPrice String required Regular price, shown struck through.
discountLabel String required Short text in the badge, such as “-50%”.
offerEndsAtMillis Long required When the offer ends, as System.currentTimeMillis() time.
legal PaywallLegal required Terms and Privacy URLs for the footer links.
onPurchase (PaywallPlan) -> Unit required Called with the selected plan when the button is tapped. Start your billing flow here.
onRestore () -> Unit required Called from the Restore link.
onClose () -> Unit required Called from the close button.
modifier Modifier Modifier Applied to the sheet.
title String “Limited-time offer” Headline.
subtitle String “A special price, just for you.” Line under the headline.
offerDurationMillis Long 15 * 60_000L Full length of the offer. Scales the progress bar.
sheetState SheetState Hidden, skips the half-expanded state Sheet state, if you need to control it.

Full signature: LimitedOfferSheet in the API reference.

Intro prices need the full story: put “First year only, then $29.99 per year” in priceDetail. onClose is called both by the close button and by swiping the sheet down.

  • The countdown is announced as one phrase (“Offer ends in 12 min 30 sec”) instead of separate digits.
  • The sheet has a close button in addition to swipe-to-dismiss.
  • Resetting the deadline on every launch. Fake urgency breaks consumer protection rules; store the real end time.
  • Leaving out the regular price after the intro period.

Built by Sanikant. Set in Google Sans Flex (SIL Open Font License). Not affiliated with Google.