NTP-65804 | add in app rating pop in coin redemption screen (#16628)

This commit is contained in:
Venkat Praneeth Reddy
2025-06-18 17:08:28 +05:30
committed by GitHub
parent df04393ded
commit 47c60844dd
6 changed files with 168 additions and 2 deletions

View File

@@ -137,6 +137,11 @@ fun CoinHistoryScreen(
)
}
// Track if userData came from bundle (for app rating popup logic)
var isUserFromCoinRedemption by remember {
mutableStateOf(bundle.getStringSafely(USER_DATA) != null)
}
var isLeftToRightTransition by remember {
val isLeftToRightTransition by bundle.bundleDelegate(defaultValue = true)
mutableStateOf(isLeftToRightTransition)
@@ -401,7 +406,14 @@ fun CoinHistoryScreen(
}
userData?.let { it ->
if (!isReferralShareScreen) {
RewardsShareScreen(navigator = navigator, userData = it) { userData = null }
RewardsShareScreen(
navigator = navigator,
userData = it,
isUserFromCoinRedemption = isUserFromCoinRedemption,
) {
userData = null
isUserFromCoinRedemption = false
}
} else {
ReferralShareScreen(
navigator = navigator,

View File

@@ -74,8 +74,12 @@ import com.navi.coin.utils.constant.ImageConstants.SHAREABILITY_LEFT_POLYGON_URL
import com.navi.coin.utils.constant.ImageConstants.SHAREABILITY_RIGHT_POLYGON_URL
import com.navi.coin.vm.RewardsShareScreenVm
import com.navi.common.R
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.APP_RATING_POP_UP_DELAY_INTERVAL
import com.navi.common.ui.compose.GratificationLottieAnimation
import com.navi.common.uitron.model.action.CtaAction
import com.navi.common.utils.PlayStoreInAppRatingHelper
import com.navi.common.utils.log
import com.navi.design.font.FontWeightEnum
import com.navi.design.font.getFontWeight
import com.navi.design.font.naviFontFamily
@@ -83,6 +87,7 @@ import com.navi.design.theme.FF1F002A
import com.navi.design.theme.WhiteFFFFFF
import com.navi.naviwidgets.R as WidgetR
import com.navi.rr.referral.models.ReferralContactList
import com.navi.rr.utils.RewardsAppRatingHelper
import com.navi.rr.utils.capturable
import com.navi.rr.utils.ext.clickable
import com.navi.rr.utils.ext.hideGenericShareLoader
@@ -94,6 +99,7 @@ import com.navi.rr.utils.handleContactClick
import com.navi.rr.utils.rememberCaptureController
import com.navi.uitron.utils.transformations.moneyFormat
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeApi::class)
@@ -102,6 +108,7 @@ fun RewardsShareScreen(
userData: ReferralContactList? = null,
navigator: DestinationsNavigator,
rewardsShareScreenVm: RewardsShareScreenVm = hiltViewModel(),
isUserFromCoinRedemption: Boolean = false,
onClose: () -> Unit,
) {
val context = LocalContext.current as CoinActivity
@@ -113,6 +120,13 @@ fun RewardsShareScreen(
val coroutineScope = rememberCoroutineScope()
var absoluteTranslationY by remember { mutableFloatStateOf(2000f) }
var showAppRatingPopUp by remember { mutableStateOf(false) }
// show app rating pop up only when share screen is triggered post redemption
val shouldShowAppRatingPopUp = remember {
isUserFromCoinRedemption && RewardsAppRatingHelper.shouldShowAppRatingPopup()
}
Init(
screenName = Constants.SCREENS.REWARDS_SHARE_SCREEN_SCREEN_NAME,
activity = context,
@@ -120,6 +134,15 @@ fun RewardsShareScreen(
navigator = navigator,
)
val playStoreRatingHelper =
remember(shouldShowAppRatingPopUp) {
if (shouldShowAppRatingPopUp) {
PlayStoreInAppRatingHelper(context, "rewards_share_screen")
} else {
null
}
}
BackHandler { absoluteTranslationY = 2000f }
val translateYState by
animateFloatAsState(
@@ -144,6 +167,24 @@ fun RewardsShareScreen(
LaunchedEffect(Unit) { rewardsShareScreenVm.inflateData(userData) { message = it } }
// delay is to ensure playStoreRatingHelper initialisation completes
LaunchedEffect(showAppRatingPopUp) {
if (shouldShowAppRatingPopUp && showAppRatingPopUp) {
delay(
FirebaseRemoteConfigHelper.getLong(
APP_RATING_POP_UP_DELAY_INTERVAL,
defaultValue = 2000,
)
)
RewardsAppRatingHelper.updateLastShownTime()
try {
playStoreRatingHelper?.inAppRating(context)
} catch (e: Exception) {
e.log()
}
}
}
LaunchedEffect(Unit) {
rewardsShareScreenVm.screenActions.collect { action ->
when (action) {
@@ -686,6 +727,7 @@ fun RewardsShareScreen(
modifier = Modifier.fillMaxSize(),
isRemoteLottie = false,
showLottieInfiniteTimes = false,
onAnimationStart = { showAppRatingPopUp = true },
lottie = R.raw.gratifaction_confetti,
)

View File

@@ -75,6 +75,7 @@ object Constants {
const val METADATA_SUBTITLE = "metadata.subtitle"
const val SCRATCH_CARD_EXPERIENCE = "SCRATCH_CARD_EXPERIENCE"
const val DRIP_REWARD_VALUE = "dripRewardValue"
const val APP_RATING_LAST_SHOWN_TIME = "APP_RATING_LAST_SHOWN_TIME"
const val SCROLL_THRESHOLD_FOR_STATUS_BAR_COLOR_CHANGE =
"SCROLL_THRESHOLD_FOR_STATUS_BAR_COLOR_CHANGE"
const val SCROLL_THRESHOLD_FOR_STATUS_BAR_COLOR_CHANGE_IN_DP = 120

View File

@@ -289,6 +289,9 @@ object FirebaseRemoteConfigHelper {
const val OKHTTP_CUSTOM_DNS_EXPERIMENT_V2 = "OKHTTP_CUSTOM_DNS_EXPERIMENT_V2"
const val OKHTTP_RETRY_EXPERIMENT = "OKHTTP_RETRY_EXPERIMENT"
const val OKHTTP_CUSTOM_DNS_VALUE = "OKHTTP_CUSTOM_DNS_VALUE"
const val APP_RATING_DAYS_INTERVAL = "APP_RATING_DAYS_INTERVAL"
const val MIN_REWARD_AMOUNT_FOR_APP_RATING = "MIN_REWARD_AMOUNT_FOR_APP_RATING"
const val APP_RATING_POP_UP_DELAY_INTERVAL = "APP_RATING_POP_UP_DELAY_INTERVAL"
// Events for firebase config
private const val FIREBASE_REMOTE_CONFIG_FETCH_SUCCESS =

View File

@@ -51,6 +51,8 @@ import com.navi.common.R
import com.navi.common.uitron.model.action.ApiType
import com.navi.common.uitron.model.action.CtaAction
import com.navi.common.utils.Constants
import com.navi.common.utils.PlayStoreInAppRatingHelper
import com.navi.common.utils.log
import com.navi.rr.common.constants.SCRATCH_CARD_GRATIFICATION_SCREEN
import com.navi.rr.common.views.NaviRRLottieAnimationWithTimeout
import com.navi.rr.common.widgetFactory.WidgetRenderer
@@ -63,6 +65,7 @@ import com.navi.rr.scratchcard.utils.ScratchCardTheme.ThemeConfig.Companion.SCRA
import com.navi.rr.scratchcard.utils.getLottieForScratchCard
import com.navi.rr.scratchcard.utils.isFestiveTheme
import com.navi.rr.scratchcard.vm.ScratchCardVM
import com.navi.rr.utils.RewardsAppRatingHelper
import com.navi.rr.utils.composeutils.Init
import com.navi.rr.utils.constants.Constants.AUTO_REDEEM_KEY
import com.navi.rr.utils.constants.Constants.BACK
@@ -104,6 +107,23 @@ fun ScratchCardComposable(
.orEmpty()
.isNotEmpty()
var showAppRatingPopUp by remember { mutableStateOf(false) }
val rewardAmount = screenContent?.scratchCardResponse?.amount
val shouldShowAppRatingPopup =
remember(rewardAmount) {
RewardsAppRatingHelper.shouldShowAppRatingPopupForReward(rewardAmount)
}
val playStoreRatingHelper =
remember(shouldShowAppRatingPopup) {
if (shouldShowAppRatingPopup) {
PlayStoreInAppRatingHelper(context, "scratch_card_composable")
} else {
null
}
}
screenContent?.scratchCardResponse?.rewardRefId?.let { scratchCardVM.setRequestId(it) }
scratchCardVM.setScratchCardBackResponse(ScratchCardBackResponse.NotOpened)
@@ -171,6 +191,17 @@ fun ScratchCardComposable(
)
}
LaunchedEffect(showAppRatingPopUp) {
if (shouldShowAppRatingPopup && showAppRatingPopUp) {
RewardsAppRatingHelper.updateLastShownTime()
try {
playStoreRatingHelper?.inAppRating(context)
} catch (e: Exception) {
e.log()
}
}
}
BoxWithConstraints(
modifier =
Modifier.fillMaxSize()
@@ -193,7 +224,10 @@ fun ScratchCardComposable(
showLottieInfiniteTimes = false,
lottieUrl = lottieUrl,
lottie = getLottieForScratchCard(themeValue = theme),
onAnimationEnd = { showConfetti = false },
onAnimationEnd = {
showConfetti = false
showAppRatingPopUp = true
},
placeHolder = R.raw.gratifaction_confetti,
)
}

View File

@@ -0,0 +1,74 @@
/*
*
* * Copyright © 2024-2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.rr.utils
import com.navi.base.sharedpref.PreferenceManager
import com.navi.base.utils.TrustedTimeAccessor
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.APP_RATING_DAYS_INTERVAL
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.MIN_REWARD_AMOUNT_FOR_APP_RATING
object RewardsAppRatingHelper {
private const val ONE_DAY_IN_MILLI_SECONDS = 24 * 60 * 60 * 1000L
private const val APP_RATING_LAST_SHOWN_TIME = "APP_RATING_LAST_SHOWN_TIME"
/**
* Determines whether the app rating popup should be shown based on:
* 1. Time elapsed since last shown
* 2. Firebase remote config threshold
*
* @return true if popup should be shown, false otherwise
*/
fun shouldShowAppRatingPopup(): Boolean {
val lastUpdatedTime =
PreferenceManager.getLongPreference(APP_RATING_LAST_SHOWN_TIME, defValue = 0L)
val currentTimeInMillis = TrustedTimeAccessor.getCurrentTimeMillis()
val daysThreshold =
FirebaseRemoteConfigHelper.getLong(APP_RATING_DAYS_INTERVAL, defaultValue = 90)
if (daysThreshold < 0) {
return false
}
val timeDifferenceInMillis = currentTimeInMillis - lastUpdatedTime
val daysSinceLastShown = timeDifferenceInMillis / ONE_DAY_IN_MILLI_SECONDS
return when {
daysSinceLastShown >= daysThreshold -> true
else -> false
}
}
/**
* Determines whether the app rating popup should be shown for reward-based scenarios. Checks
* both time-based conditions and reward amount threshold.
*
* @param rewardAmount The reward amount to validate against minimum threshold
* @return true if popup should be shown, false otherwise
*/
fun shouldShowAppRatingPopupForReward(rewardAmount: Int?): Boolean {
if (rewardAmount == null || rewardAmount <= 0) {
return false
}
val minRewardAmount =
FirebaseRemoteConfigHelper.getLong(MIN_REWARD_AMOUNT_FOR_APP_RATING, defaultValue = 100)
return (rewardAmount >= minRewardAmount) && shouldShowAppRatingPopup()
}
/**
* Updates the last shown time to current time. Call this when the app rating popup is actually
* displayed.
*/
fun updateLastShownTime() {
val currentTimeInMillis = TrustedTimeAccessor.getCurrentTimeMillis()
PreferenceManager.setLongPreference(APP_RATING_LAST_SHOWN_TIME, currentTimeInMillis)
}
}