NTP-37254 | Ads fallback views (#14921)

Signed-off-by: Naman Khurmi <naman.khurmi@navi.com>
This commit is contained in:
Naman Khurmi
2025-02-12 20:21:42 +05:30
committed by GitHub
parent b5a89122e9
commit 68b361d317
6 changed files with 418 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
/*
*
* * Copyright © 2021-2024 by Navi Technologies Limited
* * Copyright © 2021-2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
@@ -23,4 +23,5 @@ object DeeplinkConstants {
const val APP_UPDATE = "APP_UPDATE"
const val INVESTMENT = "investment"
const val NPAY_INTENT_ACTIVITY = "NPAY_INTENT_ACTIVITY"
const val NAVI_BBPS = "naviBbps"
}

View File

@@ -0,0 +1,92 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.common.adverse.fallbackTemplates
import android.app.Activity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
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.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.navi.base.deeplink.DeepLinkManager
import com.navi.base.deeplink.util.DeeplinkConstants
import com.navi.base.model.CtaData
import com.navi.common.R
import com.navi.common.adverse.model.FallbackBannerContent
import com.navi.common.utils.Constants.KEY_REDIRECTION_CTA
import com.navi.elex.atoms.ElexText
import com.navi.elex.font.FontWeightEnum
@Composable
fun BbpsBannerFallbackAd(modifier: Modifier, showExploreMore: Boolean, activity: Activity) {
Column(modifier, verticalArrangement = Arrangement.spacedBy(16.dp)) {
ExploreMoreDivider(showExploreMore)
FallbackBanner(
content =
FallbackBannerContent(
productInfo =
FallbackBannerContent.ProductInfo(
icon =
"https://public-assets.prod.navi-sa.in/adverse/bbps/bbps_loud_ad_promo_image.png",
name = null,
showRewardsCallout = true,
),
contentInfo =
FallbackBannerContent.ContentInfo(
headline = "₹0 platform fee on bill payments and recharges",
description = "Recharge, credit card \n" + "and more",
ctaText = "Pay now",
illustration =
"https://public-assets.prod.navi-sa.in/adverse/bbps/bbps_loud_ad_product_image.png",
),
bannerStyle =
FallbackBannerContent.BannerStyle.default.copy(
backgroundColors = listOf(Color(0xffCAFFE7), Color(0xffFAFFFC))
),
),
onClick = {
DeepLinkManager.getDeepLinkListener()
?.navigateTo(
activity = activity,
ctaData =
CtaData(type = KEY_REDIRECTION_CTA, url = DeeplinkConstants.NAVI_BBPS),
finish = true,
)
},
)
}
}
@Composable
fun ExploreMoreDivider(showExploreMore: Boolean) {
if (showExploreMore) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Spacer(Modifier.weight(1f).height(1.dp).background(Color(0xffF3F1F3)))
ElexText(
stringResource(R.string.explore_more),
color = Color(0xff757375),
fontSize = 12.sp,
lineHeight = 20.sp,
letterSpacing = 1.5.sp,
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
)
Spacer(Modifier.weight(1f).height(1.dp).background(Color(0xffF3F1F3)))
}
}
}

View File

@@ -0,0 +1,253 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.common.adverse.fallbackTemplates
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.navi.common.R
import com.navi.common.adverse.model.FallbackBannerContent
import com.navi.elex.atoms.ElexText
import com.navi.elex.font.FontWeightEnum
import com.navi.naviwidgets.R as NaviWidgetsR
private val LocalBannerTheme = staticCompositionLocalOf {
FallbackBannerContent.BannerStyle.default
}
@Composable
fun FallbackBanner(
modifier: Modifier = Modifier,
content: FallbackBannerContent,
onClick: () -> Unit = {},
) {
CompositionLocalProvider(LocalBannerTheme provides content.bannerStyle) {
Column(
modifier =
modifier
.height(236.dp)
.clip(RoundedCornerShape(8.dp))
.background(
brush =
Brush.horizontalGradient(
colors = LocalBannerTheme.current.backgroundColors
),
shape = RoundedCornerShape(8.dp),
)
.border(
width = 1.dp,
color = LocalBannerTheme.current.borderColor,
shape = RoundedCornerShape(8.dp),
)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(bounded = true),
) {
onClick()
},
horizontalAlignment = Alignment.Start,
) {
Column(
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
ProductHeader(content.productInfo)
ProductHeadline(content.contentInfo.headline)
}
Column(
modifier = Modifier.fillMaxSize().padding(top = 8.dp, start = 16.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
) {
BannerContent(content.contentInfo)
}
}
}
}
@Composable
private fun ProductHeader(productInfo: FallbackBannerContent.ProductInfo) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Row(
modifier =
Modifier.background(Color.White, RoundedCornerShape(4.dp))
.padding(horizontal = 3.8.dp, vertical = 2.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
AsyncImage(
model =
ImageRequest.Builder(LocalContext.current)
.allowHardware(false)
.placeholder(NaviWidgetsR.drawable.image_placeholder_small)
.error(NaviWidgetsR.drawable.image_placeholder_small)
.data(productInfo.icon)
.build(),
contentDescription = "",
modifier = Modifier.height(20.dp),
)
productInfo.name?.let { name ->
ElexText(
text = name,
fontSize = 10.sp,
lineHeight = 20.sp,
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
color = LocalBannerTheme.current.productNameColor,
letterSpacing = 1.5.sp,
)
}
}
RewardsCalloutRow(productInfo.showRewardsCallout)
}
}
@Composable
private fun RewardsCalloutRow(showRewardsCallout: Boolean) {
if (showRewardsCallout) {
Row(
modifier =
Modifier.background(
color = LocalBannerTheme.current.rewardsCalloutBackgroundColor,
shape = RoundedCornerShape(50),
)
.padding(horizontal = 8.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
ElexText(
stringResource(R.string.get_upto),
fontSize = 12.sp,
lineHeight = 16.sp,
color = LocalBannerTheme.current.rewardsCalloutTextColor,
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
)
Image(
painter = painterResource(NaviWidgetsR.drawable.navi_coin_40),
contentDescription = null,
modifier = Modifier.size(12.dp),
)
ElexText(
"1,000",
fontSize = 12.sp,
lineHeight = 16.sp,
color = LocalBannerTheme.current.rewardsCalloutTextColor,
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
)
}
}
}
@Composable
private fun ProductHeadline(productHeadline: String) {
ElexText(
productHeadline,
fontSize = 16.sp,
lineHeight = 24.sp,
color = LocalBannerTheme.current.headlineColor,
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
)
}
@Composable
private fun BannerContent(content: FallbackBannerContent.ContentInfo) {
Row(
Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(9.dp),
verticalAlignment = Alignment.Top,
) {
Column(
modifier = Modifier.align(Alignment.Top).weight(1f),
horizontalAlignment = Alignment.Start,
) {
ElexText(
content.description,
fontSize = 12.sp,
lineHeight = 16.sp,
color = LocalBannerTheme.current.descriptionColor,
fontWeight = FontWeightEnum.NAVI_BODY_REGULAR,
)
Spacer(Modifier.height(20.dp))
CtaButton(content.ctaText)
if (content.showPoweredBy) {
Spacer(Modifier.height(16.dp))
ElexText(
stringResource(R.string.powered_by_navi_finserv),
fontSize = 6.sp,
lineHeight = 16.sp,
color = LocalBannerTheme.current.poweredByColor,
fontWeight = FontWeightEnum.NAVI_BODY_REGULAR,
)
}
}
AsyncImage(
model =
ImageRequest.Builder(LocalContext.current)
.allowHardware(false)
.placeholder(NaviWidgetsR.drawable.image_placeholder_xx_large)
.error(NaviWidgetsR.drawable.image_placeholder_xx_large)
.data(content.illustration)
.build(),
contentDescription = null,
modifier = Modifier.align(Alignment.Bottom).size(114.dp),
)
}
}
@Composable
private fun CtaButton(buttonText: String) {
Box(
contentAlignment = Alignment.Center,
modifier =
Modifier.background(
color = LocalBannerTheme.current.ctaBackgroundColor,
shape = RoundedCornerShape(4.dp),
)
.padding(vertical = 12.dp, horizontal = 8.dp),
) {
ElexText(
text = buttonText,
fontSize = 12.sp,
lineHeight = 16.sp,
color = LocalBannerTheme.current.ctaTextColor,
fontWeight = FontWeightEnum.NAVI_BODY_DEMI_BOLD,
)
}
}

View File

@@ -0,0 +1,59 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.common.adverse.model
import androidx.compose.ui.graphics.Color
data class FallbackBannerContent(
val productInfo: ProductInfo,
val contentInfo: ContentInfo,
val bannerStyle: BannerStyle,
) {
data class ProductInfo(
val icon: Any,
val name: String? = null,
val showRewardsCallout: Boolean,
)
data class ContentInfo(
val headline: String,
val description: String,
val ctaText: String,
val illustration: Any,
val showPoweredBy: Boolean = false,
)
data class BannerStyle(
val backgroundColors: List<Color>,
val borderColor: Color,
val productNameColor: Color,
val rewardsCalloutBackgroundColor: Color,
val rewardsCalloutTextColor: Color,
val headlineColor: Color,
val descriptionColor: Color,
val ctaBackgroundColor: Color,
val ctaTextColor: Color,
val poweredByColor: Color,
) {
companion object {
val default =
BannerStyle(
backgroundColors = listOf(Color.White, Color.White),
borderColor = Color(0xffEBEBEB),
ctaBackgroundColor = Color(0xff1f002a),
ctaTextColor = Color.White,
headlineColor = Color(0xff191919),
descriptionColor = Color(0xff444444),
productNameColor = Color(0xff444444),
rewardsCalloutBackgroundColor = Color.White,
rewardsCalloutTextColor = Color(0xff191919),
poweredByColor = Color.Black,
)
}
}
}

View File

@@ -215,4 +215,8 @@
<string name="arc_info_bbps_bottom_sheet_title">Protects bill payments</string>
<string name="arc_info_bbps_bottom_sheet_campaign_title">If bill / recharge paid via Navi UPI is pending for %s+ days, get 100%% extra up to</string>
<string name="get_upto">Get upto</string>
<string name="powered_by_navi_finserv">Powered by Navi Finserv</string>
<string name="explore_more">EXPLORE MORE</string>
</resources>

View File

@@ -60,6 +60,7 @@ import androidx.compose.ui.window.Popup
import coil.compose.AsyncImage
import com.navi.adverse.sdk.ui.AdverseViewRoot
import com.navi.common.R as CommonR
import com.navi.common.adverse.fallbackTemplates.BbpsBannerFallbackAd
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PAY_PPS_CROSS_SELL_AD_FALLBACK_TIMEOUT
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PAY_PPS_CROSS_SELL_AD_RE_ID
@@ -190,6 +191,13 @@ fun SharedTransitionScope.PaymentSummaryTransactionDetailSection(
FirebaseRemoteConfigHelper.getLong(
NAVI_PAY_PPS_CROSS_SELL_AD_FALLBACK_TIMEOUT
),
fallbackView = {
BbpsBannerFallbackAd(
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
activity = naviPayActivity,
showExploreMore = true,
)
},
)
if (showRewardsPopUp) {
Popup {