TP-56773 reverse penny drop in PL journey (#10149)

Co-authored-by: soumya-ranjan_navi <soumya.ranjan@navi.com>
This commit is contained in:
Anmol Agrawal
2024-03-27 11:31:12 +05:30
committed by GitHub
parent 81b7f10665
commit bf705b0900
22 changed files with 672 additions and 4 deletions

View File

@@ -19,6 +19,7 @@ import com.navi.ap.common.models.customwidget.DynamicGridWidgetData
import com.navi.ap.common.models.customwidget.DynamicRadioGroupWithSectionsWidgetData
import com.navi.ap.common.models.customwidget.DynamicRowWidgetData
import com.navi.ap.common.models.customwidget.MappedRadioListWidgetData
import com.navi.ap.common.models.customwidget.RadioGroupGridWidgetData
import com.navi.ap.common.models.customwidget.StepTrackerWidgetData
import com.navi.uitron.deserializer.UiTronDataDeserializer
import com.navi.uitron.model.data.UiTronData
@@ -57,6 +58,8 @@ class CustomUiTronDataDeserializer : UiTronDataDeserializer() {
context?.deserialize(json, CameraWidgetData::class.java)
CustomWidgets.CALENDAR_WIDGET.name ->
context?.deserialize(json, CalendarWidgetData::class.java)
CustomWidgets.RADIO_GROUP_GRID_WIDGET.name ->
context?.deserialize(json, RadioGroupGridWidgetData::class.java)
else -> super.deserialize(json, typeOfT, context)
}
}

View File

@@ -8,6 +8,7 @@
package com.navi.ap.common.handler
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import androidx.activity.compose.ManagedActivityResultLauncher
@@ -24,6 +25,8 @@ import com.navi.ap.utils.getResolvedFieldValue
import com.navi.common.uitron.model.action.LaunchDefaultIntentAction
import com.navi.common.uitron.model.action.LaunchIntentAction
import com.navi.common.uitron.model.action.LaunchIntentType
import com.navi.common.uitron.model.action.LaunchTargetAppIntentAction
import com.navi.common.utils.log
@Composable
fun HandleLaunchIntentAction(viewModel: ApplicationPlatformVM) {
@@ -37,7 +40,7 @@ fun HandleLaunchIntentAction(viewModel: ApplicationPlatformVM) {
handleLaunchIntentAction(
launchIntentAction = action,
intentResult = intentResult,
viewModel = viewModel,
viewModel = viewModel
)
}
else -> Unit
@@ -68,6 +71,31 @@ private fun handleLaunchIntentAction(
intentResult.launch(intent)
}
}
LaunchIntentType.OPEN_TARGET_APP.name -> {
(launchIntentAction.value as? LaunchTargetAppIntentAction)?.let { it ->
var uri = it.intentUriString?.value
it.intentUriString?.let {
val resolvedValues =
getResolvedFieldValue(fields = listOf(it), handle = viewModel.handle)
uri = resolvedValues[it.sourceProperty]?.toString()
}
val intent =
Intent().apply {
action = it.intentAction
data = Uri.parse(uri)
}
intent.setPackage(it.targetAppPackageName)
try {
intentResult.launch(intent)
} catch (e: ActivityNotFoundException) {
viewModel.handleActions(it.onInitiateFailure)
e.log()
} catch (e: Exception) {
viewModel.handleActions(it.onInitiateFailure)
e.log()
}
}
}
else -> {}
}
}

View File

@@ -71,6 +71,18 @@ open class PLLambdaHandler(
lambdaApiAction
)
}
LambdaType.FETCH_FILTERED_UPI_APPS.name -> {
lambdaImpl.fetchFilteredUPIApps(
lambadaData.resolvedValue.toMutableMap(),
lambdaApiAction,
)
}
LambdaType.FETCH_RPD_PAYMENT_DETAILS.name -> {
lambdaImpl.fetchRPDPaymentDetails(bridge.getSaveStateHandle(), lambdaApiAction)
}
LambdaType.POST_RPD_STATUS.name -> {
lambdaImpl.postRPDStatus(lambdaApiAction)
}
LambdaType.VALIDATE_IFSC.name -> {
lambdaImpl.validateIFSC(
bridge.getSaveStateHandle(),

View File

@@ -19,6 +19,7 @@ import com.navi.ap.common.models.lambdamodels.ConsentRequest
import com.navi.ap.common.models.lambdamodels.RPDTokenRequestDetails
import com.navi.ap.common.models.lambdamodels.request.EmiModel
import com.navi.ap.common.models.lambdamodels.request.FetchPaymentMethodsRequest
import com.navi.ap.common.models.lambdamodels.request.InstalledUpiApps
import com.navi.ap.common.models.lambdamodels.request.KycMetadata
import com.navi.ap.common.models.lambdamodels.request.KycSdkVerificationRequestData
import com.navi.ap.common.models.lambdamodels.request.OfferDetails
@@ -77,10 +78,12 @@ import com.navi.ap.utils.constants.OFFER_TYPE
import com.navi.ap.utils.constants.PG_TOKEN
import com.navi.ap.utils.constants.PIN_CODE
import com.navi.ap.utils.constants.PREFERRED_BANKS_LIST
import com.navi.ap.utils.constants.PREFERRED_UPI_APPS_LIST
import com.navi.ap.utils.constants.RATE_OF_INTEREST
import com.navi.ap.utils.constants.RPD_PAYMENT_METHOD_ID
import com.navi.ap.utils.constants.RPD_PAYMENT_METHOD_TOKEN
import com.navi.ap.utils.constants.RPD_PAYMENT_METHOD_UPI_LINK_KEY
import com.navi.ap.utils.constants.RPD_PAYMENT_URI
import com.navi.ap.utils.constants.S3_KEY
import com.navi.ap.utils.constants.SEARCH_DEBOUNCE_TIME_IN_MILLIS
import com.navi.ap.utils.constants.SELECTED_BANK
@@ -111,6 +114,7 @@ import com.navi.common.uitron.model.action.LambdaApiAction
import com.navi.common.uitron.model.action.SDKType
import com.navi.common.utils.Constants.APP_PLATFORM_APPLICATION_ID
import com.navi.common.utils.Constants.APP_PLATFORM_APPLICATION_TYPE
import com.navi.common.utils.Constants.LIST_OF_INSTALLED_UPI_APPS
import com.navi.common.utils.deviceId
import com.navi.common.utils.toJsonObject
import com.navi.naviwidgets.utils.FORWARD_SLASH
@@ -599,6 +603,156 @@ internal class PLLambdaImpl(
}
}
fun fetchFilteredUPIApps(
resolvedValues: MutableMap<String, Any?>,
lambdaApiAction: LambdaApiAction,
) {
bridge.getViewModelScope().launch(Dispatchers.IO) {
if (lambdaHandler.getClonedScreenDefinitionState() == null) {
lambdaHandler.setClonedScreenDefinitionState(
bridge.getScreenStructurePreRenderState()
)
}
bridge.setLambdaState(LambdaState.Loading)
val installedUpiApps = resolvedValues[LIST_OF_INSTALLED_UPI_APPS]
val keyForPreferredUPIAppsListData =
resolvedValues[PREFERRED_UPI_APPS_LIST]?.toString().orEmpty()
val installedUpiAppsModel =
InstalledUpiApps(
listOfInstalledApps = installedUpiApps,
apApplicationId = bridge.getQueryMap()[APP_PLATFORM_APPLICATION_ID]
)
bridge.executeActions(lambdaApiAction.preExecutionAction)
val lambdaResponse = repository.fetchFilteredUPIApps(installedUpiAppsModel)
bridge.setLambdaState(
when {
lambdaResponse.data.isNotNull() &&
lambdaHandler.getClonedScreenDefinitionState().isNotNull() -> {
bridge.getSaveStateHandle()[keyForPreferredUPIAppsListData] =
lambdaResponse.data?.listOfEnabledApps
val updatedResponse =
PathInjector<ApScreenDefinitionStructure, Any?>()
.injectData(
lambdaHandler.getClonedScreenDefinitionState(),
lambdaResponse.data
)
val updatedLambdaApiAction =
PathInjector<LambdaApiAction, Any?>()
.injectData(lambdaApiAction, lambdaResponse.data)
updatedResponse?.let {
bridge.setScreenDefinitionState(
ApScreenDefinitionState.PreRenderLoading(updatedResponse)
)
}
bridge.executeActions(updatedLambdaApiAction.onSuccess)
LambdaState.Success(LambdaResponseType())
}
lambdaResponse.errorBottomSheetStructure.isNotNull() -> {
bridge.executeActions(lambdaApiAction.onFailure)
LambdaState.Error(
lambdaResponse.statusCode,
lambdaResponse.errorBottomSheetStructure,
lambdaResponse.genericErrorBottomSheetFields
)
}
lambdaResponse.data.isNull() -> {
getErrorBottomSheetStructureAndSetLambdaState(
ApiType.GetScreenDefinition.name,
lambdaResponse.statusCode
)
}
else -> {
LambdaState.Nothing
}
}
)
}
}
fun fetchRPDPaymentDetails(
handle: SavedStateHandle,
lambdaApiAction: LambdaApiAction,
) {
bridge.getViewModelScope().launch(Dispatchers.IO) {
bridge.setLambdaState(LambdaState.Loading)
val token = handle.get<String>(RPD_PAYMENT_METHOD_TOKEN).orEmpty()
bridge.executeActions(lambdaApiAction.preExecutionAction)
val lambdaResponse = repository.fetchRPDPaymentDetails(token = token)
bridge.setLambdaState(
when {
lambdaResponse.data.isNotNull() -> {
val uri =
lambdaResponse.data
?.methodDetails
?.providerConfigDetails
?.upiLink
.orEmpty()
handle[RPD_PAYMENT_URI] = uri
bridge.executeActions(lambdaApiAction.onSuccess)
LambdaState.Success(LambdaResponseType())
}
lambdaResponse.errorBottomSheetStructure.isNotNull() -> {
bridge.executeActions(lambdaApiAction.onFailure)
LambdaState.Error(
lambdaResponse.statusCode,
lambdaResponse.errorBottomSheetStructure,
lambdaResponse.genericErrorBottomSheetFields
)
}
lambdaResponse.data.isNull() -> {
getErrorBottomSheetStructureAndSetLambdaState(
ApiType.GetScreenDefinition.name,
lambdaResponse.statusCode
)
}
else -> {
LambdaState.Nothing
}
}
)
}
}
fun postRPDStatus(
lambdaApiAction: LambdaApiAction,
) {
bridge.getViewModelScope().launch(Dispatchers.IO) {
bridge.setLambdaState(LambdaState.Loading)
val handle = bridge.getSaveStateHandle()
val token = handle.get<String>(RPD_PAYMENT_METHOD_TOKEN).orEmpty()
val lambdaResponse = repository.postRPDStatus(token = token)
bridge.setLambdaState(
when {
lambdaResponse.data.isNotNull() -> {
val updatedLambdaApiAction =
PathInjector<LambdaApiAction, Any?>()
.injectData(lambdaApiAction, lambdaResponse.data)
bridge.executeActions(updatedLambdaApiAction.onSuccess)
LambdaState.Success(LambdaResponseType())
}
lambdaResponse.errorBottomSheetStructure.isNotNull() -> {
bridge.executeActions(lambdaApiAction.onFailure)
LambdaState.Error(
lambdaResponse.statusCode,
lambdaResponse.errorBottomSheetStructure,
lambdaResponse.genericErrorBottomSheetFields
)
}
lambdaResponse.data.isNull() -> {
getErrorBottomSheetStructureAndSetLambdaState(
ApiType.LambdaApiAction.name,
lambdaResponse.statusCode
)
}
else -> {
LambdaState.Nothing
}
}
)
}
}
fun validateIFSC(
handle: SavedStateHandle,
lambdaApiAction: LambdaApiAction,

View File

@@ -15,11 +15,14 @@ import com.navi.ap.common.models.lambdamodels.RPDTokenData
import com.navi.ap.common.models.lambdamodels.RPDTokenRequestDetails
import com.navi.ap.common.models.lambdamodels.request.EmiModel
import com.navi.ap.common.models.lambdamodels.request.FetchPaymentMethodsRequest
import com.navi.ap.common.models.lambdamodels.request.InstalledUpiApps
import com.navi.ap.common.models.lambdamodels.request.OfferDetails
import com.navi.ap.common.models.lambdamodels.request.RPDPaymentData
import com.navi.ap.common.models.lambdamodels.request.TelcoResendOtpRequest
import com.navi.ap.common.models.lambdamodels.response.AgreementLetterDownloadResponse
import com.navi.ap.common.models.lambdamodels.response.ApplyLoanResponse
import com.navi.ap.common.models.lambdamodels.response.BankDataResponse
import com.navi.ap.common.models.lambdamodels.response.FilteredUPIAppsResponse
import com.navi.ap.common.models.lambdamodels.response.IfscBranchResponse
import com.navi.ap.common.models.lambdamodels.response.KYCTrackerItemsListData
import com.navi.ap.common.models.lambdamodels.response.KycSdkVerificationResponse
@@ -157,6 +160,34 @@ class PLLambdaRepository @Inject constructor(private val retrofitService: APRetr
return apiResponseCallback(response = response, apiTag = ApiType.LambdaApiAction.name)
}
suspend fun fetchFilteredUPIApps(
installedApps: InstalledUpiApps
): ApRepoResult<FilteredUPIAppsResponse> {
val response = retrofitService.sendUPIApps(data = installedApps, target = ORCHESTRATION_PL)
logApiResponseEvents(methodName = ::fetchFilteredUPIApps.name, response = response)
return apiResponseCallback(response = response, apiTag = ApiType.GetScreenDefinition.name)
}
suspend fun fetchRPDPaymentDetails(token: String): ApRepoResult<RPDPaymentData> {
val response =
retrofitService.fetchRPDPaymentDetails(
token = token,
target = Constants.PAYMENT_GATEWAY_MODULE
)
logApiResponseEvents(methodName = ::fetchRPDPaymentDetails.name, response = response)
return apiResponseCallback(response = response, apiTag = ApiType.GetScreenDefinition.name)
}
suspend fun postRPDStatus(token: String): ApRepoResult<Any> {
val response =
retrofitService.postRPDStatus(
token = token,
target = Constants.PAYMENT_GATEWAY_MODULE,
)
logApiResponseEvents(methodName = ::postRPDStatus.name, response = response)
return apiResponseCallback(response = response, apiTag = ApiType.LambdaApiAction.name)
}
suspend fun validateIFSC(ifscCode: String): ApRepoResult<IfscBranchResponse> {
val response = retrofitService.validateIFSC(ifscCode = ifscCode)
logApiResponseEvents(methodName = ::validateIFSC.name, response = response)

View File

@@ -55,4 +55,5 @@ enum class CustomWidgets {
DYNAMIC_RADIO_GROUP_WITH_SECTIONS_WIDGET,
CAMERA,
CALENDAR_WIDGET,
RADIO_GROUP_GRID_WIDGET
}

View File

@@ -0,0 +1,46 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.ap.common.models.customwidget
import com.navi.uitron.model.data.ImageData
import com.navi.uitron.model.data.UiTronActionData
import com.navi.uitron.model.data.UiTronData
import com.navi.uitron.model.ui.ArrangementData
import com.navi.uitron.model.ui.ComposePadding
import com.navi.uitron.model.ui.ImageProperty
import com.navi.uitron.model.ui.LazyGridProperty
import com.navi.uitron.model.ui.TextProperty
data class RadioGroupGridWidgetData(
val gridData: GridWidgetData? = null,
val itemProperties: GridItem? = null,
val itemData: GridItemData? = null,
val selectedAction: UiTronActionData? = null,
) : UiTronData()
data class GridItem(
val mainImageProperty: ImageProperty? = null,
val selectedImageProperty: ImageProperty? = null,
val titleTextProperty: TextProperty? = null,
val spaceBetweenImageAndText: Int? = 0,
)
data class GridItemData(
val selectedImageData: ImageData? = null,
)
data class GridWidgetData(
val minGridSize: Int? = 100,
val maxGridSize: Int? = 1000,
val gridCell: LazyGridProperty.GridCell? = null,
val orientation: String? = LazyGridProperty.ORIENTATION_VERTICAL,
val padding: ComposePadding? = null,
val horizontalArrangementData: ArrangementData? = null,
val verticalArrangementData: ArrangementData? = null,
val defaultItemSelectedIndex: Int = -1
)

View File

@@ -0,0 +1,15 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.ap.common.models.lambdamodels.request
import com.google.gson.annotations.SerializedName
data class InstalledUpiApps(
@SerializedName("listOfInstalledApps") val listOfInstalledApps: Any? = null,
@SerializedName("apApplicationId") val apApplicationId: String? = null
)

View File

@@ -0,0 +1,26 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.ap.common.models.lambdamodels.request
import com.google.gson.annotations.SerializedName
data class RPDPaymentData(
@SerializedName("responseType") val responseType: String? = null,
@SerializedName("actionType") val actionType: String? = null,
@SerializedName("methodDetails") val methodDetails: MethodDetails? = null
) {
data class MethodDetails(
@SerializedName("provider") val provider: String? = null,
@SerializedName("providerConfigDetails")
val providerConfigDetails: ProviderConfigDetails? = null
) {
data class ProviderConfigDetails(
@SerializedName("upiLink") val upiLink: String? = null,
)
}
}

View File

@@ -0,0 +1,24 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.ap.common.models.lambdamodels.response
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class AppMetaData(
@SerializedName("appPackageName") val appPackageName: String? = null,
@SerializedName("appIconUrl") val appIconUrl: String? = null,
@SerializedName("appName") val appName: String? = null,
) : Parcelable
data class FilteredUPIAppsResponse(
@SerializedName("listOfEnabledApps") val listOfEnabledApps: List<AppMetaData>? = null,
@SerializedName("rpdPossible") val rpdPossible: Boolean? = null
)

View File

@@ -0,0 +1,252 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.ap.common.renderer
import android.annotation.SuppressLint
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.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import com.navi.ap.common.models.WidgetModelDefinition
import com.navi.ap.common.models.customwidget.GridItem
import com.navi.ap.common.models.customwidget.GridItemData
import com.navi.ap.common.models.customwidget.RadioGroupGridWidgetData
import com.navi.ap.common.models.lambdamodels.response.AppMetaData
import com.navi.ap.common.viewmodel.ApplicationPlatformVM
import com.navi.ap.utils.constants.MAX_SIZE
import com.navi.ap.utils.constants.MIN_SIZE
import com.navi.ap.utils.constants.WIDGET_DATA
import com.navi.uitron.model.UiTronResponse
import com.navi.uitron.model.data.ImageData
import com.navi.uitron.model.data.TextData
import com.navi.uitron.model.data.UiTronActionData
import com.navi.uitron.model.ui.ImageProperty
import com.navi.uitron.model.ui.TextProperty
import com.navi.uitron.render.ImageRenderer
import com.navi.uitron.render.TextRenderer
import com.navi.uitron.utils.getContentPaddingValues
import com.navi.uitron.utils.hexToComposeColor
import com.navi.uitron.utils.setVerticalArrangement
class RadioGroupGridWidget {
@Composable
fun Render(viewModel: ApplicationPlatformVM, widget: WidgetModelDefinition<UiTronResponse>) {
val widgetData = widget.widgetData?.data
val radioGroupGridWidgetData =
widgetData?.get("$WIDGET_DATA${widget.widgetId}") as? RadioGroupGridWidgetData
val listData =
viewModel.handle
.getStateFlow("${UPI_RADIO_GROUP_DATA}${widget.widgetId}", emptyList<AppMetaData>())
.collectAsState()
.value
val gridWidgetData = radioGroupGridWidgetData?.gridData
val selectedRadioIndex = remember {
mutableIntStateOf(gridWidgetData?.defaultItemSelectedIndex ?: -1)
}
LazyVerticalGrid(
modifier =
Modifier.heightIn(
gridWidgetData?.minGridSize?.dp ?: MIN_SIZE.dp,
gridWidgetData?.maxGridSize?.dp ?: MAX_SIZE.dp
),
columns = GridCells.Fixed(4),
contentPadding = getContentPaddingValues(gridWidgetData?.padding),
verticalArrangement =
Arrangement.setVerticalArrangement(
arrangementData = gridWidgetData?.verticalArrangementData
)
) {
itemsIndexed(listData) { index, item ->
val isSelected = index == selectedRadioIndex.intValue
GridItem(
radioGroupGridWidgetData?.itemProperties,
radioGroupGridWidgetData?.itemData,
selectedRadioIndex,
index,
item,
isSelected,
radioGroupGridWidgetData?.selectedAction,
viewModel
)
}
}
}
@Composable
private fun GridItem(
itemProperty: GridItem?,
itemData: GridItemData?,
selectedRadioIndex: MutableIntState,
index: Int,
item: Any?,
isSelected: Boolean,
selectedAction: UiTronActionData?,
viewModel: ApplicationPlatformVM
) {
item as AppMetaData
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
UpiItem(
selectedRadioIndex,
index,
isSelected,
itemProperty,
item,
itemData,
selectedAction,
viewModel
)
Spacer(modifier = Modifier.height(itemProperty?.spaceBetweenImageAndText?.dp ?: 0.dp))
TitleText(itemProperty, item, viewModel)
}
}
@Composable
private fun UpiItem(
selectedRadioIndex: MutableIntState,
index: Int,
isSelected: Boolean,
itemProperty: GridItem?,
item: Any?,
itemData: GridItemData?,
selectedAction: UiTronActionData?,
viewModel: ApplicationPlatformVM
) {
item as AppMetaData
Box {
UpiIcon(
isSelected,
itemProperty,
item,
viewModel,
Modifier.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
selectedRadioIndex.intValue = index
viewModel.handle[SELECTED_PACKAGE_NAME_KEY] = item.appPackageName
viewModel.handle[SELECTED_APP_NAME_KEY] = item.appName
viewModel.handleActions(selectedAction)
}
)
UpiItemSelectedIcon(isSelected, itemProperty, itemData, viewModel)
}
}
@Composable
private fun TitleText(
itemProperty: GridItem?,
item: AppMetaData,
viewModel: ApplicationPlatformVM
) {
TextRenderer()
.Render(
property = itemProperty?.titleTextProperty ?: TextProperty(),
uiTronData = TextData(item.appName),
uiTronViewModel = viewModel,
modifier = Modifier
)
}
@Composable
private fun BoxScope.UpiItemSelectedIcon(
isSelected: Boolean,
itemProperty: GridItem?,
itemData: GridItemData?,
viewModel: ApplicationPlatformVM
) {
if (isSelected)
ImageRenderer()
.Render(
property = itemProperty?.selectedImageProperty ?: ImageProperty(),
uiTronData = itemData?.selectedImageData,
uiTronViewModel = viewModel,
modifier =
upiItemSelectedIconModifier(
itemProperty?.selectedImageProperty?.width?.toFloat()?.div(4) ?: 0f,
itemProperty?.selectedImageProperty?.height?.toFloat()?.div(4) ?: 0f,
Alignment.TopEnd
)
)
}
@SuppressLint("ModifierFactoryExtensionFunction")
private fun BoxScope.upiItemSelectedIconModifier(
translateX: Float,
translateY: Float,
alignment: Alignment
): Modifier {
return Modifier.align(alignment).graphicsLayer {
translationX = translateX
translationY = -translateY
}
}
@Composable
private fun UpiIcon(
isSelected: Boolean,
itemProperty: GridItem?,
item: Any?,
viewModel: ApplicationPlatformVM,
modifier: Modifier
) {
item as AppMetaData
val selectedColor =
itemProperty?.mainImageProperty?.backgroundColor?.hexToComposeColor ?: Color(0xff1f002a)
val unselectedColor =
itemProperty?.mainImageProperty?.backgroundColor?.hexToComposeColor ?: Color(0xffF0F0F0)
Column(
modifier =
modifier.border(
width = 1.dp,
color = if (isSelected) selectedColor else unselectedColor,
shape = RoundedCornerShape(8.dp)
)
) {
ImageRenderer()
.Render(
property = itemProperty?.mainImageProperty ?: ImageProperty(),
uiTronData = ImageData(item.appIconUrl),
uiTronViewModel = viewModel,
modifier = Modifier
)
}
}
companion object {
const val UPI_RADIO_GROUP_DATA = "upi_radio_group_data_"
const val SELECTED_PACKAGE_NAME_KEY = "rpd.selectedUpiAppPackageName"
const val SELECTED_APP_NAME_KEY = "rpd.selectedUpiAppName"
}
}

View File

@@ -19,6 +19,7 @@ import com.navi.ap.common.models.customwidget.DynamicGridWidgetData
import com.navi.ap.common.models.customwidget.DynamicRadioGroupWithSectionsWidgetData
import com.navi.ap.common.models.customwidget.DynamicRowWidgetData
import com.navi.ap.common.models.customwidget.MappedRadioListWidgetData
import com.navi.ap.common.models.customwidget.RadioGroupGridWidgetData
import com.navi.ap.common.models.customwidget.StepTrackerWidgetData
import com.navi.uitron.model.data.UiTronData
import com.navi.uitron.serializer.UiTronDataSerializer
@@ -73,6 +74,12 @@ class CustomUiTronDataSerializer : UiTronDataSerializer() {
CustomWidgets.CALENDAR_WIDGET.name -> {
context?.serialize(src as CalendarWidgetData, CalendarWidgetData::class.java)
}
CustomWidgets.RADIO_GROUP_GRID_WIDGET.name -> {
context?.serialize(
src as RadioGroupGridWidgetData,
RadioGroupGridWidgetData::class.java
)
}
else -> super.serialize(src, typeOfSrc, context)
}
}

View File

@@ -19,6 +19,7 @@ import com.navi.ap.common.renderer.DynamicGridWidget
import com.navi.ap.common.renderer.DynamicRadioGroupWithSectionsWidget
import com.navi.ap.common.renderer.DynamicRowWidget
import com.navi.ap.common.renderer.MappedRadioListWidget
import com.navi.ap.common.renderer.RadioGroupGridWidget
import com.navi.ap.common.renderer.StepTrackerWidget
import com.navi.ap.common.viewmodel.ApplicationPlatformVM
import com.navi.uitron.model.UiTronResponse
@@ -60,6 +61,9 @@ fun CustomWidgetRenderer(
CustomWidgets.CALENDAR_WIDGET.name -> {
CalendarWidget().Render(viewModel = viewModel, widget = widget)
}
CustomWidgets.RADIO_GROUP_GRID_WIDGET.name -> {
RadioGroupGridWidget().Render(viewModel = viewModel, widget = widget)
}
else -> Unit
}
}

View File

@@ -14,11 +14,14 @@ import com.navi.ap.common.models.lambdamodels.RPDTokenData
import com.navi.ap.common.models.lambdamodels.RPDTokenRequestDetails
import com.navi.ap.common.models.lambdamodels.request.EmiModel
import com.navi.ap.common.models.lambdamodels.request.FetchPaymentMethodsRequest
import com.navi.ap.common.models.lambdamodels.request.InstalledUpiApps
import com.navi.ap.common.models.lambdamodels.request.OfferDetails
import com.navi.ap.common.models.lambdamodels.request.RPDPaymentData
import com.navi.ap.common.models.lambdamodels.request.TelcoResendOtpRequest
import com.navi.ap.common.models.lambdamodels.response.AgreementLetterDownloadResponse
import com.navi.ap.common.models.lambdamodels.response.ApplyLoanResponse
import com.navi.ap.common.models.lambdamodels.response.BankDataResponse
import com.navi.ap.common.models.lambdamodels.response.FilteredUPIAppsResponse
import com.navi.ap.common.models.lambdamodels.response.IfscBranchResponse
import com.navi.ap.common.models.lambdamodels.response.KYCTrackerItemsListData
import com.navi.ap.common.models.lambdamodels.response.KycSdkVerificationResponse
@@ -106,6 +109,25 @@ interface PlLambdaService {
@Path("methodId") methodId: String
): Response<GenericResponse<RPDPaymentMethodDetails>>
@POST("/payment-methods")
suspend fun sendUPIApps(
@Body data: InstalledUpiApps,
@Header("X-Target") target: String
): Response<GenericResponse<FilteredUPIAppsResponse>>
@POST("/api/v2/get-methods")
suspend fun fetchRPDPaymentDetails(
@Header("X-Target") target: String,
@Header("X-Payment-SDK-Token") token: String,
@Header("X-Payment-SDK-Version") version: String = Constants.PAYMENT_SDK_VERSION
): Response<GenericResponse<RPDPaymentData>>
@POST("api/v1/signal/v2/checkStatus")
suspend fun postRPDStatus(
@Header("X-Target") target: String,
@Header("X-Payment-SDK-Token") token: String
): Response<GenericResponse<Any>>
@GET("/banks/{code}")
suspend fun validateIFSC(
@Header("X-Target") target: String = ModuleName.LE.name,

View File

@@ -45,6 +45,7 @@ import com.navi.common.uitron.model.action.FillApiData
import com.navi.common.uitron.model.action.SourceType
import com.navi.common.utils.CommonUtils
import com.navi.common.utils.EMPTY
import com.navi.common.utils.getDeviceData
import com.navi.common.utils.log
import com.navi.uitron.model.UiTronResponse
import com.navi.uitron.utils.getInputId
@@ -184,6 +185,15 @@ fun getResolvedFieldValue(
val output = readJsonPath(obj.toJson(), item.jsonPath.orEmpty())
map[item.sourceProperty.orEmpty()] = "$output$valueSuffix"
}
SourceType.DEVICE_PROPERTY.name -> {
val value =
getDeviceData(
sourceProperty = item.sourceProperty.orEmpty(),
context = CommonLibManager.application.applicationContext,
additionalValues = item.additionalParameters
)
map[item.name.orEmpty()] = value
}
else -> Unit
}
}

View File

@@ -55,6 +55,9 @@ enum class LambdaType {
APPLY_LOAN,
FETCH_RPD_TOKEN,
FETCH_RPD_PAYMENT_METHOD_DETAILS,
FETCH_FILTERED_UPI_APPS,
FETCH_RPD_PAYMENT_DETAILS,
POST_RPD_STATUS,
FETCH_CONSENT_DATA,
FETCH_BANKS,
VALIDATE_IFSC,

View File

@@ -25,6 +25,8 @@ const val FINARKEIN_CONSENT_DATA = "finarkein_consent_data"
const val RPD_PAYMENT_METHOD_TOKEN = "rpdPaymentMethodToken"
const val RPD_PAYMENT_METHOD_ID = "rpdPaymentMethodId"
const val RPD_PAYMENT_METHOD_UPI_LINK_KEY = "upiLink"
const val PREFERRED_UPI_APPS_LIST = "preferred_upi_apps_list"
const val RPD_PAYMENT_URI = "rpd_payment_uri_link_key"
const val ALL_BANKS_LIST = "all_banks_list"
const val PREFERRED_BANKS_LIST = "preferred_banks_list"

View File

@@ -7,6 +7,7 @@
package com.navi.base.utils
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -558,4 +559,18 @@ object BaseUtils {
val timeStamp: String = SimpleDateFormat(datePattern, Locale.getDefault()).format(Date())
return File(storageDir, "JPEG_${timeStamp}.jpg")
}
@SuppressLint("QueryPermissionsNeeded")
fun getUPIAppsInstalledOnDevice(activity: Context): List<String> {
val packageManager = activity.packageManager
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(UPI_APP_INTENT_URL)
val activities =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL)
} else {
packageManager.queryIntentActivities(intent, INT_ZERO)
}
return activities.map { it.activityInfo.packageName }
}
}

View File

@@ -13,6 +13,7 @@ import com.google.gson.JsonElement
import com.navi.common.uitron.model.action.LaunchDefaultIntentAction
import com.navi.common.uitron.model.action.LaunchIntentAction
import com.navi.common.uitron.model.action.LaunchIntentType
import com.navi.common.uitron.model.action.LaunchTargetAppIntentAction
import java.lang.reflect.Type
class UiTronLaunchIntentActionDeserializer : JsonDeserializer<LaunchIntentAction> {
@@ -27,6 +28,8 @@ class UiTronLaunchIntentActionDeserializer : JsonDeserializer<LaunchIntentAction
return when (type.asString) {
LaunchIntentType.DEFAULT.name ->
context?.deserialize(jsonObject, LaunchDefaultIntentAction::class.java)
LaunchIntentType.OPEN_TARGET_APP.name ->
context?.deserialize(jsonObject, LaunchTargetAppIntentAction::class.java)
else -> null
}
}

View File

@@ -21,7 +21,8 @@ open class LaunchIntentAction(
@SerializedName("intentUriString") val intentUriString: FillApiData? = null,
@SerializedName("onSuccess") val onSuccess: UiTronActionData? = null,
@SerializedName("onFailure") val onFailure: UiTronActionData? = null,
@SerializedName("onExit") val onExit: UiTronActionData? = null
@SerializedName("onExit") val onExit: UiTronActionData? = null,
@SerializedName("onInitiateFailure") val onInitiateFailure: UiTronActionData? = null,
) : UiTronAction(), Parcelable {
override suspend fun manageAction(actionDetails: ActionDetails) {
val action = actionDetails.uiTronAction as LaunchIntentAction
@@ -31,6 +32,11 @@ open class LaunchIntentAction(
class LaunchDefaultIntentAction : LaunchIntentAction()
class LaunchTargetAppIntentAction(
@SerializedName("targetAppPackageName") val targetAppPackageName: String? = null,
) : LaunchIntentAction()
enum class LaunchIntentType {
DEFAULT
DEFAULT,
OPEN_TARGET_APP
}

View File

@@ -232,6 +232,7 @@ object Constants {
const val CONTENT_SIZE_IN_KB = "content_size_kb"
const val API_METRIC_EVENT = "dev_api_metric_event"
const val IS_UPI_APP_AVAILABLE = "isUpiAppAvailable"
const val LIST_OF_INSTALLED_UPI_APPS = "LIST_OF_INSTALLED_UPI_APPS"
const val ANIMATION_DURATION_IN_MILLIS = 400
const val EIGHT = 8
const val FINISH = "finish"

View File

@@ -9,16 +9,18 @@ package com.navi.common.utils
import android.content.Context
import com.navi.base.model.LineItem
import com.navi.base.utils.BaseUtils.getUPIAppsInstalledOnDevice
import com.navi.base.utils.BaseUtils.isUpiAppExceptParticularAppsAvailable
import com.navi.common.constants.DEVICE_ID
import com.navi.common.utils.Constants.IS_DEVICE_ROOTED
import com.navi.common.utils.Constants.IS_UPI_APP_AVAILABLE
import com.navi.common.utils.Constants.LIST_OF_INSTALLED_UPI_APPS
fun getDeviceData(
sourceProperty: String,
context: Context,
additionalValues: List<LineItem>?
): String {
): Any {
return when (sourceProperty) {
DEVICE_ID -> deviceId
IS_DEVICE_ROOTED -> CommonRootDeviceUtil.instance.isDeviceRooted().toString()
@@ -28,6 +30,7 @@ fun getDeviceData(
exceptionUpiApps = additionalValues?.map { it.value.orEmpty() }.orEmpty()
)
.toString()
LIST_OF_INSTALLED_UPI_APPS -> getUPIAppsInstalledOnDevice(context)
else -> EMPTY
}
}