Skip to content

Commit

Permalink
Add claimed winback offer UI (#3435)
Browse files Browse the repository at this point in the history
  • Loading branch information
MiSikora authored Jan 15, 2025
1 parent 8ef0f8a commit 25d05dc
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,60 +1,199 @@
package au.com.shiftyjelly.pocketcasts.profile.winback

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.coerceAtMost
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import au.com.shiftyjelly.pocketcasts.compose.AppThemeWithBackground
import au.com.shiftyjelly.pocketcasts.compose.Devices
import au.com.shiftyjelly.pocketcasts.compose.buttons.RowButton
import au.com.shiftyjelly.pocketcasts.compose.components.TextP30
import au.com.shiftyjelly.pocketcasts.compose.preview.ThemePreviewParameterProvider
import au.com.shiftyjelly.pocketcasts.compose.theme
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme.ThemeType
import au.com.shiftyjelly.pocketcasts.images.R as IR
import au.com.shiftyjelly.pocketcasts.localization.R as LR

@Composable
internal fun OfferClaimedPage(
theme: ThemeType,
onConfirm: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.fillMaxSize()
.background(Color(0xFFCA9CA9))
.padding(16.dp),
.nestedScroll(rememberNestedScrollInteropConnection())
.verticalScroll(rememberScrollState()),
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.height(80.dp)
.fillMaxWidth(),
) {
Text(
text = "Offer Claimed",
color = Color.Black,
Spacer(
modifier = Modifier.height(52.dp),
)
BoxWithConstraints {
SparkleImage(
gradientColors = theme.sparkleColors,
modifier = Modifier.size((maxWidth * 0.4f).coerceAtMost(162.dp)),
)
}
Spacer(
modifier = Modifier.height(20.dp),
)
Text(
text = stringResource(LR.string.winback_claimed_offer_message_1),
fontWeight = FontWeight.Bold,
fontSize = 28.sp,
lineHeight = 38.5.sp,
color = MaterialTheme.theme.colors.primaryText01,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 16.dp),
)
Spacer(
modifier = Modifier.height(16.dp),
)
TextP30(
text = stringResource(LR.string.winback_claimed_offer_message_2),
color = MaterialTheme.theme.colors.primaryText02,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 24.dp),
)
Spacer(
modifier = Modifier.weight(1f),
)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.height(64.dp)
.fillMaxWidth()
.background(Color(0xFFCC5079))
.clickable(onClick = onConfirm),
) {
Text(
text = "Done",
color = Color.White,
)
}
RowButton(
text = stringResource(LR.string.done),
onClick = onConfirm,
)
Spacer(
modifier = Modifier.height(48.dp),
)
}
}

@Composable
private fun SparkleImage(
gradientColors: Pair<Color, Color>,
modifier: Modifier = Modifier,
) {
BoxWithConstraints(
modifier = modifier,
) {
SparkleIcon(
painter = painterResource(IR.drawable.ic_sparkle_1),
width = maxWidth * 0.425f,
height = maxHeight * 0.457f,
offsetX = maxWidth * 0.42f,
offsetY = maxHeight * 0.1f,
gradientColors = gradientColors,
alpha = 1f,
)
SparkleIcon(
painter = painterResource(IR.drawable.ic_sparkle_2),
width = maxWidth * 0.242f,
height = maxHeight * 0.259f,
offsetX = maxWidth * 0.15f,
offsetY = maxHeight * 0.35f,
gradientColors = gradientColors,
alpha = 0.8f,
)
SparkleIcon(
painter = painterResource(IR.drawable.ic_sparkle_3),
width = maxWidth * 0.29f,
height = maxHeight * 0.32f,
offsetX = maxWidth * 0.32f,
offsetY = maxHeight * 0.57f,
gradientColors = gradientColors,
alpha = 0.6f,
)
}
}

@Composable
private fun SparkleIcon(
painter: Painter,
width: Dp,
height: Dp,
offsetX: Dp,
offsetY: Dp,
gradientColors: Pair<Color, Color>,
alpha: Float,
) {
Image(
painter = painter,
contentDescription = null,
modifier = Modifier
.graphicsLayer {
compositingStrategy = CompositingStrategy.Offscreen
this.alpha = alpha
}
.padding(start = offsetX, top = offsetY)
.size(width, height)
.drawWithContent {
drawContent()
drawRect(
brush = Brush.verticalGradient(
colors = listOf(gradientColors.first, gradientColors.second),
),
blendMode = BlendMode.SrcAtop,
)
},
)
}

private val ThemeType.sparkleColors
get() = when (this) {
ThemeType.LIGHT, ThemeType.DARK, ThemeType.EXTRA_DARK, ThemeType.ELECTRIC -> blueSparkle
ThemeType.CLASSIC_LIGHT, ThemeType.ROSE -> redSparkle
ThemeType.INDIGO -> indigoSparkle
ThemeType.DARK_CONTRAST -> graySparkle
ThemeType.LIGHT_CONTRAST -> blackSparkle
ThemeType.RADIOACTIVE -> greenSparkle
}

private val blackSparkle = Color.Black to Color(0xFF6B7273)
private val blueSparkle = Color(0xFF03A9F4) to Color(0xFF50D0F1)
private val redSparkle = Color(0xFFF43769) to Color(0xFFFB5246)
private val indigoSparkle = Color(0xFF5C8BCC) to Color(0xFF95B0E6)
private val greenSparkle = Color(0xFF78D549) to Color(0xFF9BE45E)
private val graySparkle = Color(0xFFCCD6D9) to Color(0xFFE5F7FF)

@Preview(device = Devices.PortraitRegular)
@Composable
private fun WinbackOfferPagePreview(
@PreviewParameter(ThemePreviewParameterProvider::class) theme: ThemeType,
) {
AppThemeWithBackground(theme) {
OfferClaimedPage(
theme = theme,
onConfirm = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,21 @@ class WinbackFragment : BaseDialogFragment() {
) {
composable(WinbackNavRoutes.WinbackOffer) {
WinbackOfferPage(
onClaimOffer = { navController.navigate(WinbackNavRoutes.OfferClaimed) },
onClaimOffer = {
navController.navigate(WinbackNavRoutes.OfferClaimed) {
popUpTo(WinbackNavRoutes.WinbackOffer) {
inclusive = true
}
}
},
onSeeAvailablePlans = { navController.navigate(WinbackNavRoutes.AvailablePlans) },
onSeeHelpAndFeedback = { navController.navigate(WinbackNavRoutes.HelpAndFeedback) },
onContinueToCancellation = { navController.navigate(WinbackNavRoutes.CancelConfirmation) },
)
}
composable(WinbackNavRoutes.OfferClaimed) {
OfferClaimedPage(
theme = theme.activeTheme,
onConfirm = { dismiss() },
)
}
Expand Down Expand Up @@ -85,14 +92,17 @@ private fun AnimatedContentTransitionScope<NavBackStackEntry>.slideInToStart() =
towards = AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = animationSpec,
)

private fun AnimatedContentTransitionScope<NavBackStackEntry>.slideOutToStart() = slideOutOfContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = animationSpec,
)

private fun AnimatedContentTransitionScope<NavBackStackEntry>.slideInToEnd() = slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.End,
animationSpec = animationSpec,
)

private fun AnimatedContentTransitionScope<NavBackStackEntry>.slideOutToEnd() = slideOutOfContainer(
towards = AnimatedContentTransitionScope.SlideDirection.End,
animationSpec = animationSpec,
Expand Down
10 changes: 10 additions & 0 deletions modules/services/images/src/main/res/drawable/ic_sparkle_1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="69dp"
android:height="75dp"
android:viewportWidth="69"
android:viewportHeight="75">
<path
android:fillColor="#FF00FF"
android:pathData="M24.437,26.104L30.228,3.849C31.378,-0.57 37.652,-0.57 38.802,3.849L44.593,26.104C45.011,27.71 46.291,28.947 47.909,29.311L65.476,33.256C70.088,34.292 70.088,40.864 65.476,41.9L47.909,45.846C46.291,46.209 45.011,47.446 44.593,49.052L38.802,71.307C37.652,75.726 31.378,75.726 30.228,71.307L24.437,49.052C24.019,47.446 22.739,46.209 21.12,45.846L3.555,41.9C-1.057,40.864 -1.057,34.292 3.555,33.256L21.12,29.311C22.739,28.947 24.019,27.71 24.437,26.104Z" />
</vector>
10 changes: 10 additions & 0 deletions modules/services/images/src/main/res/drawable/ic_sparkle_2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="41dp"
android:height="43dp"
android:viewportWidth="41"
android:viewportHeight="43">
<path
android:fillColor="#FF00FF"
android:pathData="M13.362,13.928L17.681,2.171C18.629,-0.409 22.278,-0.409 23.225,2.171L27.544,13.928C27.861,14.791 28.562,15.457 29.439,15.73L37.968,18.383C40.735,19.244 40.735,23.162 37.968,24.023L29.439,26.676C28.562,26.949 27.861,27.615 27.544,28.478L23.225,40.235C22.278,42.815 18.629,42.815 17.681,40.235L13.362,28.478C13.045,27.615 12.345,26.949 11.467,26.676L2.939,24.023C0.171,23.162 0.171,19.244 2.939,18.383L11.467,15.73C12.345,15.457 13.045,14.791 13.362,13.928Z" />
</vector>
10 changes: 10 additions & 0 deletions modules/services/images/src/main/res/drawable/ic_sparkle_3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="48dp"
android:height="53dp"
android:viewportWidth="48"
android:viewportHeight="53">
<path
android:fillColor="#FF00FF"
android:pathData="M16.19,17.77L21.13,2.652C22.017,-0.062 25.858,-0.062 26.744,2.652L31.685,17.77C31.972,18.648 32.653,19.341 33.526,19.644L45.407,23.756C48.056,24.673 48.056,28.42 45.407,29.337L33.526,33.45C32.653,33.752 31.972,34.445 31.685,35.323L26.744,50.442C25.858,53.156 22.017,53.156 21.13,50.442L16.19,35.323C15.903,34.445 15.222,33.752 14.349,33.45L2.468,29.337C-0.181,28.42 -0.181,24.673 2.468,23.756L14.349,19.644C15.222,19.341 15.903,18.648 16.19,17.77Z" />
</vector>
2 changes: 2 additions & 0 deletions modules/services/localization/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2284,5 +2284,7 @@
<string name="winback_offer_help_title">Need help with Pocket&#160;Casts?</string>
<string name="winback_offer_help_description">Struggling with any features or having issues.</string>
<string name="winback_offer_cancel_continue_button_label">Continue to Cancellation</string>
<string name="winback_claimed_offer_message_1">Enjoy your free month!</string>
<string name="winback_claimed_offer_message_2">Thanks for choosing Pocket&#160;Casts. Billing starts after your free month ends.</string>

</resources>

0 comments on commit 25d05dc

Please sign in to comment.