diff --git a/android/navi-base/src/main/java/com/navi/base/deeplink/util/DeeplinkConstants.kt b/android/navi-base/src/main/java/com/navi/base/deeplink/util/DeeplinkConstants.kt index fb614cb919..48873e14a8 100644 --- a/android/navi-base/src/main/java/com/navi/base/deeplink/util/DeeplinkConstants.kt +++ b/android/navi-base/src/main/java/com/navi/base/deeplink/util/DeeplinkConstants.kt @@ -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" } diff --git a/android/navi-common/src/main/java/com/navi/common/adverse/fallbackTemplates/BannerTemplates.kt b/android/navi-common/src/main/java/com/navi/common/adverse/fallbackTemplates/BannerTemplates.kt new file mode 100644 index 0000000000..c5b64e13af --- /dev/null +++ b/android/navi-common/src/main/java/com/navi/common/adverse/fallbackTemplates/BannerTemplates.kt @@ -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))) + } + } +} diff --git a/android/navi-common/src/main/java/com/navi/common/adverse/fallbackTemplates/FallbackBanner.kt b/android/navi-common/src/main/java/com/navi/common/adverse/fallbackTemplates/FallbackBanner.kt new file mode 100644 index 0000000000..563946e429 --- /dev/null +++ b/android/navi-common/src/main/java/com/navi/common/adverse/fallbackTemplates/FallbackBanner.kt @@ -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, + ) + } +} diff --git a/android/navi-common/src/main/java/com/navi/common/adverse/model/FallbackBannerContent.kt b/android/navi-common/src/main/java/com/navi/common/adverse/model/FallbackBannerContent.kt new file mode 100644 index 0000000000..7f380d2632 --- /dev/null +++ b/android/navi-common/src/main/java/com/navi/common/adverse/model/FallbackBannerContent.kt @@ -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, + 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, + ) + } + } +} diff --git a/android/navi-common/src/main/res/values/strings.xml b/android/navi-common/src/main/res/values/strings.xml index 69d572f515..1f926828e2 100644 --- a/android/navi-common/src/main/res/values/strings.xml +++ b/android/navi-common/src/main/res/values/strings.xml @@ -215,4 +215,8 @@ Protects bill payments If bill / recharge paid via Navi UPI is pending for %s+ days, get 100%% extra up to + Get upto + Powered by Navi Finserv + EXPLORE MORE + diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/paymentsummary/ui/PaymentSummaryTransactionDetailSection.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/paymentsummary/ui/PaymentSummaryTransactionDetailSection.kt index fbbd69b5b1..0bb42b4cc4 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/paymentsummary/ui/PaymentSummaryTransactionDetailSection.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/paymentsummary/ui/PaymentSummaryTransactionDetailSection.kt @@ -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 {