diff --git a/modules/features/profile/build.gradle.kts b/modules/features/profile/build.gradle.kts index d6c615a41cb..e09c207d17c 100644 --- a/modules/features/profile/build.gradle.kts +++ b/modules/features/profile/build.gradle.kts @@ -59,6 +59,7 @@ dependencies { implementation(libs.lifecycle.reactivestreams.ktx) implementation(libs.media3.datasource) implementation(libs.media3.extractor) + implementation(libs.navigation.compose) implementation(libs.play.cast) implementation(libs.rx2.java) implementation(libs.rx2.kotlin) diff --git a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/AccountDetailsFragment.kt b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/AccountDetailsFragment.kt index 5352b592e77..0754164ecc0 100644 --- a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/AccountDetailsFragment.kt +++ b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/AccountDetailsFragment.kt @@ -24,6 +24,7 @@ import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTracker import au.com.shiftyjelly.pocketcasts.preferences.Settings import au.com.shiftyjelly.pocketcasts.profile.champion.PocketCastsChampionBottomSheetDialog +import au.com.shiftyjelly.pocketcasts.profile.winback.WinbackFragment import au.com.shiftyjelly.pocketcasts.repositories.playback.PlaybackManager import au.com.shiftyjelly.pocketcasts.repositories.playback.UpNextQueue import au.com.shiftyjelly.pocketcasts.repositories.podcast.EpisodeManager @@ -41,6 +42,8 @@ import au.com.shiftyjelly.pocketcasts.ui.helper.FragmentHostListener import au.com.shiftyjelly.pocketcasts.utils.Gravatar import au.com.shiftyjelly.pocketcasts.utils.Util import au.com.shiftyjelly.pocketcasts.utils.extensions.pxToDp +import au.com.shiftyjelly.pocketcasts.utils.featureflag.Feature +import au.com.shiftyjelly.pocketcasts.utils.featureflag.FeatureFlag import au.com.shiftyjelly.pocketcasts.utils.log.LogBuffer import au.com.shiftyjelly.pocketcasts.views.dialog.ConfirmationDialog import au.com.shiftyjelly.pocketcasts.views.fragments.BaseFragment @@ -148,9 +151,13 @@ class AccountDetailsFragment : BaseFragment() { }, onCancelSubscription = { analyticsTracker.track(AnalyticsEvent.ACCOUNT_DETAILS_CANCEL_TAPPED) - CancelConfirmationFragment - .newInstance() - .show(childFragmentManager, "cancel_subscription_confirmation_dialog") + if (FeatureFlag.isEnabled(Feature.WINBACK)) { + WinbackFragment().show(childFragmentManager, "subscription_windback") + } else { + CancelConfirmationFragment + .newInstance() + .show(childFragmentManager, "cancel_subscription_confirmation_dialog") + } }, onChangeNewsletterSubscription = { isChecked -> accountViewModel.updateNewsletter(isChecked) @@ -224,6 +231,7 @@ class AccountDetailsFragment : BaseFragment() { accountViewModel.clearDeleteAccountState() performSignOut() } + is DeleteAccountState.Failure -> { accountViewModel.clearDeleteAccountState() AlertDialog.Builder(requireContext()) @@ -232,6 +240,7 @@ class AccountDetailsFragment : BaseFragment() { .setPositiveButton(getString(LR.string.ok)) { dialog, _ -> dialog.dismiss() } .show() } + is DeleteAccountState.Empty -> {} } } diff --git a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/AvailablePlansPage.kt b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/AvailablePlansPage.kt new file mode 100644 index 00000000000..31296cd3021 --- /dev/null +++ b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/AvailablePlansPage.kt @@ -0,0 +1,55 @@ +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.layout.Column +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.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +internal fun AvailablePlansPage( + onGoBack: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier + .fillMaxSize() + .background(Color(0xFFEACDD0)) + .padding(16.dp), + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .background(Color(0xFFA3A6A3)) + .clickable(onClick = onGoBack) + .padding(16.dp), + ) { + Text( + text = "Back", + color = Color.Black, + ) + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(80.dp) + .fillMaxWidth(), + ) { + Text( + text = "Available Plans", + color = Color.Black, + ) + } + } +} diff --git a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/CancelConfirmationPage.kt b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/CancelConfirmationPage.kt new file mode 100644 index 00000000000..ceaf6ef2a99 --- /dev/null +++ b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/CancelConfirmationPage.kt @@ -0,0 +1,74 @@ +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.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.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +internal fun CancelConfirmationPage( + onKeepSubscription: () -> Unit, + onCancelSubscription: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier + .fillMaxSize() + .background(Color(0xFFCCE0DA)) + .padding(16.dp), + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(80.dp) + .fillMaxWidth(), + ) { + Text( + text = "Cancel Confirmation", + color = Color.Black, + ) + } + Spacer( + modifier = Modifier.weight(1f), + ) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(64.dp) + .fillMaxWidth() + .background(Color(0xFFF2E2E3)) + .clickable(onClick = onKeepSubscription), + ) { + Text( + text = "Keep subscritpion", + color = Color.Black, + ) + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(64.dp) + .fillMaxWidth() + .background(Color(0xFFEDDDD1)) + .clickable(onClick = onCancelSubscription), + ) { + Text( + text = "Cancel subscription", + color = Color.Black, + ) + } + } +} diff --git a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/HelpAndFeedbackPage.kt b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/HelpAndFeedbackPage.kt new file mode 100644 index 00000000000..0893b30a14c --- /dev/null +++ b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/HelpAndFeedbackPage.kt @@ -0,0 +1,55 @@ +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.layout.Column +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.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +internal fun HelpAndFeedbackPage( + onGoBack: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier + .fillMaxSize() + .background(Color(0xFF9BD3CB)) + .padding(16.dp), + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .background(Color(0xFF129A7D)) + .clickable(onClick = onGoBack) + .padding(16.dp), + ) { + Text( + text = "Back", + color = Color.White, + ) + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(80.dp) + .fillMaxWidth(), + ) { + Text( + text = "Help & Feedback", + color = Color.Black, + ) + } + } +} diff --git a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/OfferClaimedPage.kt b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/OfferClaimedPage.kt new file mode 100644 index 00000000000..2b3e080c5ad --- /dev/null +++ b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/OfferClaimedPage.kt @@ -0,0 +1,60 @@ +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.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.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +internal fun OfferClaimedPage( + onConfirm: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier + .fillMaxSize() + .background(Color(0xFFCA9CA9)) + .padding(16.dp), + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(80.dp) + .fillMaxWidth(), + ) { + Text( + text = "Offer Claimed", + color = Color.Black, + ) + } + 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, + ) + } + } +} diff --git a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/WinbackFragment.kt b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/WinbackFragment.kt new file mode 100644 index 00000000000..923a2ceeb28 --- /dev/null +++ b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/WinbackFragment.kt @@ -0,0 +1,99 @@ +package au.com.shiftyjelly.pocketcasts.profile.winback + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.Toast +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.IntOffset +import androidx.fragment.compose.content +import androidx.navigation.NavBackStackEntry +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import au.com.shiftyjelly.pocketcasts.compose.AppThemeWithBackground +import au.com.shiftyjelly.pocketcasts.views.fragments.BaseDialogFragment +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class WinbackFragment : BaseDialogFragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ) = content { + AppThemeWithBackground( + themeType = theme.activeTheme, + ) { + val navController = rememberNavController() + NavHost( + navController = navController, + startDestination = WinbackNavRoutes.WinbackOffer, + enterTransition = { slideInToStart() }, + exitTransition = { slideOutToStart() }, + popEnterTransition = { slideInToEnd() }, + popExitTransition = { slideOutToEnd() }, + modifier = Modifier.fillMaxSize(), + ) { + composable(WinbackNavRoutes.WinbackOffer) { + WinbackOfferPage( + onClaimOffer = { navController.navigate(WinbackNavRoutes.OfferClaimed) }, + onSeeAvailablePlans = { navController.navigate(WinbackNavRoutes.AvailablePlans) }, + onSeeHelpAndFeedback = { navController.navigate(WinbackNavRoutes.HelpAndFeedback) }, + onContinueToCancellation = { navController.navigate(WinbackNavRoutes.CancelConfirmation) }, + ) + } + composable(WinbackNavRoutes.OfferClaimed) { + OfferClaimedPage( + onConfirm = { dismiss() }, + ) + } + composable(WinbackNavRoutes.AvailablePlans) { + AvailablePlansPage( + onGoBack = { navController.popBackStack() }, + ) + } + composable(WinbackNavRoutes.HelpAndFeedback) { + HelpAndFeedbackPage( + onGoBack = { navController.popBackStack() }, + ) + } + composable(WinbackNavRoutes.CancelConfirmation) { + CancelConfirmationPage( + onKeepSubscription = { dismiss() }, + onCancelSubscription = { Toast.makeText(requireActivity(), "Go to Play Store subscription", Toast.LENGTH_LONG).show() }, + ) + } + } + } + } +} + +private object WinbackNavRoutes { + const val WinbackOffer = "WinbackOffer" + const val OfferClaimed = "OfferClaimed" + const val AvailablePlans = "AvailablePlans" + const val HelpAndFeedback = "HelpAndFeedback" + const val CancelConfirmation = "CancelConfirmation" +} + +private val animationSpec = tween(350) +private fun AnimatedContentTransitionScope.slideInToStart() = slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = animationSpec, +) +private fun AnimatedContentTransitionScope.slideOutToStart() = slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = animationSpec, +) +private fun AnimatedContentTransitionScope.slideInToEnd() = slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.End, + animationSpec = animationSpec, +) +private fun AnimatedContentTransitionScope.slideOutToEnd() = slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.End, + animationSpec = animationSpec, +) diff --git a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/WinbackOfferPage.kt b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/WinbackOfferPage.kt new file mode 100644 index 00000000000..80b67973b6c --- /dev/null +++ b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/winback/WinbackOfferPage.kt @@ -0,0 +1,102 @@ +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.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.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +internal fun WinbackOfferPage( + onClaimOffer: () -> Unit, + onSeeAvailablePlans: () -> Unit, + onSeeHelpAndFeedback: () -> Unit, + onContinueToCancellation: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier + .fillMaxSize() + .background(Color(0xFFEED7D8)) + .padding(16.dp), + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(80.dp) + .fillMaxWidth(), + ) { + Text( + text = "Winback Offer", + color = Color.Black, + ) + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(64.dp) + .fillMaxWidth() + .background(Color(0xFFD6B7B1)) + .clickable(onClick = onClaimOffer), + ) { + Text( + text = "Claim Offer", + color = Color.Black, + ) + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(64.dp) + .fillMaxWidth() + .background(Color(0xFFAE8094)) + .clickable(onClick = onSeeAvailablePlans), + ) { + Text( + text = "See Plans", + color = Color.Black, + ) + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(64.dp) + .fillMaxWidth() + .background(Color(0xFF85506E)) + .clickable(onClick = onSeeHelpAndFeedback), + ) { + Text( + text = "Help & Feedback", + color = Color.White, + ) + } + Spacer( + modifier = Modifier.weight(1f), + ) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(64.dp) + .fillMaxWidth() + .background(Color(0xFF58443E)) + .clickable(onClick = onContinueToCancellation), + ) { + Text( + text = "Cancel", + color = Color.White, + ) + } + } +} diff --git a/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt index d35d9b33f89..d4b46199710 100644 --- a/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt +++ b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt @@ -187,6 +187,14 @@ enum class Feature( hasFirebaseRemoteFlag = true, hasDevToggle = true, ), + WINBACK( + key = "winback", + title = "Winback flow", + defaultValue = BuildConfig.DEBUG, + tier = FeatureTier.Free, + hasFirebaseRemoteFlag = false, + hasDevToggle = true, + ), ; companion object {