diff --git a/App/Container/Navi-Insurance/screen/market-benefit-compare-screen/MarketBenefitCompareScreenStyles.ts b/App/Container/Navi-Insurance/screen/market-benefit-compare-screen/MarketBenefitCompareScreenStyles.ts index 7ba59549e5..afae156dcc 100644 --- a/App/Container/Navi-Insurance/screen/market-benefit-compare-screen/MarketBenefitCompareScreenStyles.ts +++ b/App/Container/Navi-Insurance/screen/market-benefit-compare-screen/MarketBenefitCompareScreenStyles.ts @@ -9,7 +9,7 @@ const styles = StyleSheet.create({ width: "100%", alignItems: "stretch", backgroundColor: "#FFFFFF", - zIndex: 1 + zIndex: 1, }, content: { flexGrow: 1, diff --git a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/insuranceTab/InsuranceTabComposables.kt b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/insuranceTab/InsuranceTabComposables.kt index e426d74108..2fa0b72705 100644 --- a/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/insuranceTab/InsuranceTabComposables.kt +++ b/android/app/src/main/java/com/naviapp/home/dashboard/ui/compose/insuranceTab/InsuranceTabComposables.kt @@ -248,7 +248,7 @@ fun ContentComposable( GenericComposableWidgetFactory( data = widgetData, widgetCallback = widgetCallback, - onWidgetUpdate = { data -> + onWidgetUpdate = { data, isValid -> val updatedList = dataList?.toMutableList() ?: mutableListOf() updatedList[index] = data dataList = updatedList diff --git a/android/navi-base/src/main/java/com/navi/base/model/CtaData.kt b/android/navi-base/src/main/java/com/navi/base/model/CtaData.kt index f875f41503..1c20f9f4a6 100644 --- a/android/navi-base/src/main/java/com/navi/base/model/CtaData.kt +++ b/android/navi-base/src/main/java/com/navi/base/model/CtaData.kt @@ -33,6 +33,7 @@ data class CtaData( @SerializedName("metaData") val metaData: GenericAnalytics? = null, @SerializedName("finish") val finish: Boolean? = null, @SerializedName("clearTask") val clearTask: Boolean? = null, + @SerializedName("clearTop") val clearTop: Boolean? = null, @SerializedName("requestCode") val requestCode: Int? = null, var bundle: Bundle? = null, @SerializedName("buttonState") val buttonState: String? = null, diff --git a/android/navi-base/src/main/java/com/navi/base/model/NaviClickAction.kt b/android/navi-base/src/main/java/com/navi/base/model/NaviClickAction.kt index 5e1ab2045f..084eed5258 100644 --- a/android/navi-base/src/main/java/com/navi/base/model/NaviClickAction.kt +++ b/android/navi-base/src/main/java/com/navi/base/model/NaviClickAction.kt @@ -186,5 +186,6 @@ enum class CtaType(val value: String?) { CALLBACK_STATUS("CALLBACK_STATUS"), DOWNLOAD_SHARE("DOWNLOAD_SHARE"), DISMISS_TOAST("DISMISS_TOAST"), - CANCEL_QUOTE("CANCEL_QUOTE") + CANCEL_QUOTE("CANCEL_QUOTE"), + RELOAD_SCREEN("RELOAD_SCREEN") } diff --git a/android/navi-common/src/main/java/com/navi/common/utils/Utility.kt b/android/navi-common/src/main/java/com/navi/common/utils/Utility.kt index 960686ed00..4fc5e0ce40 100644 --- a/android/navi-common/src/main/java/com/navi/common/utils/Utility.kt +++ b/android/navi-common/src/main/java/com/navi/common/utils/Utility.kt @@ -26,7 +26,9 @@ import android.os.UserManager import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast +import androidx.compose.ui.platform.SoftwareKeyboardController import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.getSystemService import com.google.gson.GsonBuilder import com.navi.alfred.utils.log import com.navi.analytics.utils.NaviTrackEvent @@ -233,6 +235,15 @@ fun hideKeyboard(context: Context, view: View) { } } +fun hideKeyboard( + context: Context, + view: View, + keyBoardController: SoftwareKeyboardController?, +) { + context.getSystemService()?.hideSoftInputFromWindow(view.windowToken, 0) + keyBoardController?.hide() +} + fun isDead(activity: Activity?): Boolean { return activity == null || activity.isFinishing } diff --git a/android/navi-design/src/main/java/com/navi/design/utils/Constants.kt b/android/navi-design/src/main/java/com/navi/design/utils/Constants.kt index 658764c246..13832edcd8 100644 --- a/android/navi-design/src/main/java/com/navi/design/utils/Constants.kt +++ b/android/navi-design/src/main/java/com/navi/design/utils/Constants.kt @@ -38,4 +38,12 @@ object Constants { const val RUPEE = "₹ " const val COLOR_TRANSPARENT = "#00FFFFFF" const val RADIAL_GRADIENT = "RADIAL" + const val REGENERATE_OTP_TIME = "Regenerate OTP in 00:" + const val REGENERATE_OTP = "Regenerate OTP" +} + +enum class KeyboardInputType { + NUMERIC, + PHONE, + EMAIL } diff --git a/android/navi-insurance/src/main/AndroidManifest.xml b/android/navi-insurance/src/main/AndroidManifest.xml index 95b2077c17..bedbf8fcbd 100644 --- a/android/navi-insurance/src/main/AndroidManifest.xml +++ b/android/navi-insurance/src/main/AndroidManifest.xml @@ -87,6 +87,12 @@ android:screenOrientation="portrait" android:theme="@style/GiAppTheme" /> + + () + private lateinit var scaffoldState: ScaffoldState + private var policyId: String? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { setContent { AbhaScreen() } } + } + + @Composable + fun AbhaScreen() { + Box(modifier = Modifier.fillMaxSize().background(color = Color.White)) { + val state = viewModel.abhaScreenResponse.collectAsState() + scaffoldState = rememberScaffoldState() + when (state.value) { + is ABHAFragmentVM.ViewState.Error -> { + FullScreenErrorComposeView(error = null) { viewModel.getABHAScreenResponse() } + } + ABHAFragmentVM.ViewState.Loading -> { + AbhaShimmer() + } + is ABHAFragmentVM.ViewState.Success -> { + Scaffold( + scaffoldState = scaffoldState, + topBar = { + Column { + (state.value as ABHAFragmentVM.ViewState.Success) + .data + ?.header + ?.forEach { + GenericComposableWidgetFactory( + data = it, + widgetCallback = this@ABHAFragment + ) + } + } + }, + content = { it -> + Column(modifier = Modifier.padding(it)) { + (state.value as ABHAFragmentVM.ViewState.Success) + .data + ?.content + ?.forEach { + GenericComposableWidgetFactory( + data = it, + widgetCallback = this@ABHAFragment + ) + } + } + }, + bottomBar = { + Column { + (state.value as ABHAFragmentVM.ViewState.Success) + .data + ?.footer + ?.forEach { + GenericComposableWidgetFactory( + data = it, + widgetCallback = this@ABHAFragment + ) + } + } + }, + backgroundColor = Color.White + ) + } + else -> {} + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel.setPolicyId(arguments?.getString(POLICY_ID_EXTRA)) + viewModel.setPageSource(arguments?.getString(PAGE_SOURCE)) + viewModel.setLinkAbhaRequest(getAbhaLinkRequest()) + viewModel.getABHAScreenResponse() + NaviInsuranceAnalytics.postAnalyticsEvent( + HI_ABHA_PAGE_LANDS, + mapOf(Pair(POLICY_ID_EXTRA, policyId.orEmpty())) + ) + } + + private fun getAbhaLinkRequest(): LinkAbhaRequest { + return LinkAbhaRequest( + assetId = arguments?.getString(ASSET_ID_EXTRA), + healthAssetId = arguments?.getString(HEALTH_ASSET_ID_EXTRA), + name = arguments?.getString(NAME), + proposerRelationship = arguments?.getString(PROPOSER_RELATIONSHIP_EXTRA), + abhaNumber = arguments?.getString(ABHA_NUMBER_EXTRA), + mobileNumber = arguments?.getString(MOBILE_NUMBER_EXTRA), + aadhaarNumber = arguments?.getString(AADHAR_NUMBER_EXTRA), + txnId = arguments?.getString(TXN_ID_EXTRA), + otpValue = arguments?.getString(OTP_VALUE_EXTRA) + ) + } + + override fun onClick(naviClickAction: NaviClickAction, widgetId: String?) { + when (naviClickAction) { + is CtaData -> { + naviClickAction.analyticsEventProperties?.let { + NaviInsuranceAnalytics.postAnalyticsEvent(it.name.orEmpty(), it.properties) + } + if (naviClickAction.type == CtaType.GO_BACK.value) { + activity?.onBackPressed() + } else { + NaviInsuranceDeeplinkNavigator.navigate( + this.requireActivity(), + naviClickAction, + finish = naviClickAction.finish.orFalse(), + clearTask = naviClickAction.clearTask.orFalse() + ) + } + } + } + } + + override fun getViewModel(): GiBaseVM? = null + + override fun getRetryAction(errorMessage: ErrorMessage?): (() -> Unit)? = null + + override val screenName: String = TAG + + override fun onBackPressed(): Boolean = false + + companion object { + const val TAG = "ABHA_SCREEN" + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAFragmentVM.kt b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAFragmentVM.kt new file mode 100644 index 0000000000..eb96b33956 --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAFragmentVM.kt @@ -0,0 +1,209 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.abha + +import androidx.lifecycle.viewModelScope +import com.navi.base.utils.isNotNull +import com.navi.base.utils.isNull +import com.navi.base.utils.orZero +import com.navi.common.viewmodel.BaseVM +import com.navi.insurance.models.request.LinkAbhaRequest +import com.navi.insurance.util.AADHAR_NUMBER_EXTRA +import com.navi.insurance.util.MOBILE_NUMBER_EXTRA +import com.navi.insurance.util.OTP_VALUE_EXTRA +import com.navi.naviwidgets.WidgetTypes +import com.navi.naviwidgets.composewidget.model.EditableTextWidgetData +import com.navi.naviwidgets.composewidget.model.OTPWidgetData +import com.navi.naviwidgets.composewidget.model.SelectableTextWidgetData +import com.navi.naviwidgets.models.FooterButtonState +import com.navi.naviwidgets.models.GenericWidgetDataInfo +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +@HiltViewModel +class ABHAFragmentVM +@Inject +constructor( + private val abhaRepository: ABHARepository, +) : BaseVM() { + + private val _abhaScreenResponse: MutableStateFlow> = + MutableStateFlow(ViewState.Initial) + val abhaScreenResponse: StateFlow> = _abhaScreenResponse + + private val _aadharPageResponseFlow = MutableStateFlow(AbhaPageResponseState()) + val aadharPageResponseFlow: StateFlow + get() = _aadharPageResponseFlow.asStateFlow() + + private val _otpPageResponseFlow = MutableStateFlow(AbhaPageResponseState()) + val otpPageResponseFlow: StateFlow + get() = _otpPageResponseFlow.asStateFlow() + + private val _footerState = MutableStateFlow(FooterButtonState.DISABLED.name) + val footerState: StateFlow + get() = _footerState.asStateFlow() + + private var otpPageValidation: Int = 0 + private var aadharPageValidation: Int = 0 + var validAadharWidgets: HashMap = hashMapOf() + var validOtpWidgets: HashMap = hashMapOf() + private var policyId: String? = null + private var pageSource: String? = null + private var linkAbhaRequest: LinkAbhaRequest = LinkAbhaRequest() + + fun setPolicyId(policyId: String?) { + this.policyId = policyId + } + + fun setPageSource(pageSource: String?) { + this.pageSource = pageSource + } + + fun setLinkAbhaRequest(linkAbhaRequest: LinkAbhaRequest) { + this.linkAbhaRequest = linkAbhaRequest + } + + fun getABHAScreenResponse() = + viewModelScope.launch { + _abhaScreenResponse.value = ViewState.Loading + val response = abhaRepository.fetchAbhaResponse(linkAbhaRequest, policyId, pageSource) + _abhaScreenResponse.value = + if (!response.errors.isNullOrEmpty() || response.error.isNotNull()) ViewState.Error + else ViewState.Success(response.data) + } + + fun fetchAadharResponse() { + _aadharPageResponseFlow.update { it.copy(isLoading = true) } + viewModelScope.launch(Dispatchers.IO) { + val response = abhaRepository.fetchAbhaResponse(linkAbhaRequest, policyId, pageSource) + if ( + response.error.isNull() && + response.errors.isNullOrEmpty() && + response.data.isNotNull() + ) { + validAadharWidgets = hashMapOf() + _footerState.value = FooterButtonState.DISABLED.name + response.data?.apply { + _aadharPageResponseFlow.update { + it.copy(isLoading = false, data = this, hasErrorOccured = false) + } + aadharPageValidation = metaData?.pageValidation?.validationWidgets.orZero() + } + } else { + _aadharPageResponseFlow.update { + it.copy(isLoading = false, data = null, hasErrorOccured = true) + } + setErrorData(response.errors, response.error) + } + } + } + + fun fetchOTPResponse() { + _otpPageResponseFlow.update { it.copy(isLoading = true) } + viewModelScope.launch(Dispatchers.IO) { + val response = abhaRepository.fetchAbhaResponse(linkAbhaRequest, policyId, pageSource) + if ( + response.error.isNull() && + response.errors.isNullOrEmpty() && + response.data.isNotNull() + ) { + validOtpWidgets = hashMapOf() + _footerState.value = FooterButtonState.DISABLED.name + response.data?.apply { + _otpPageResponseFlow.update { + it.copy(isLoading = false, data = this, hasErrorOccured = false) + } + otpPageValidation = metaData?.pageValidation?.validationWidgets.orZero() + } + } else { + _otpPageResponseFlow.update { + it.copy(isLoading = false, data = null, hasErrorOccured = true) + } + setErrorData(response.errors, response.error) + } + } + } + + fun updateAadharValidWidgets(widgetDataInfo: GenericWidgetDataInfo, isValid: Boolean) { + when { + isValid -> addValidWidget(widgetDataInfo) + else -> removeValidWidget(widgetDataInfo) + } + + _footerState.value = + if (validAadharWidgets.size == aadharPageValidation) { + FooterButtonState.ENABLED.name + } else { + FooterButtonState.DISABLED.name + } + } + + private fun addValidWidget(widgetDataInfo: GenericWidgetDataInfo) { + when (widgetDataInfo) { + is EditableTextWidgetData -> { + when (widgetDataInfo.widgetData?.id) { + AADHAR_NUMBER_EXTRA -> validAadharWidgets[AADHAR_NUMBER_EXTRA] = widgetDataInfo + MOBILE_NUMBER_EXTRA -> validAadharWidgets[MOBILE_NUMBER_EXTRA] = widgetDataInfo + } + } + is SelectableTextWidgetData -> { + validAadharWidgets[WidgetTypes.SELECTABLE_TEXT_WIDGET.value] = widgetDataInfo + } + } + } + + private fun removeValidWidget(widgetDataInfo: GenericWidgetDataInfo) { + when (widgetDataInfo) { + is EditableTextWidgetData -> { + when (widgetDataInfo.widgetData?.id) { + AADHAR_NUMBER_EXTRA -> validAadharWidgets.remove(AADHAR_NUMBER_EXTRA) + MOBILE_NUMBER_EXTRA -> validAadharWidgets.remove(MOBILE_NUMBER_EXTRA) + } + } + is SelectableTextWidgetData -> { + validAadharWidgets.remove(WidgetTypes.SELECTABLE_TEXT_WIDGET.value) + } + } + } + + fun updateOtpValidWidgets(widgetDataInfo: GenericWidgetDataInfo, isValid: Boolean) { + if (isValid) { + if (widgetDataInfo is OTPWidgetData) { + validOtpWidgets[OTP_VALUE_EXTRA] = widgetDataInfo + } + } else { + if (widgetDataInfo is OTPWidgetData && validOtpWidgets.containsKey(OTP_VALUE_EXTRA)) { + validOtpWidgets.remove(OTP_VALUE_EXTRA) + } + } + + if (validOtpWidgets.size == otpPageValidation) { + _footerState.value = FooterButtonState.ENABLED.name + } else { + _footerState.value = FooterButtonState.DISABLED.name + } + } + + sealed class ViewState { + data object Initial : ViewState() + + data class Success(val data: T) : ViewState() + + data object Loading : ViewState() + + data object NoInternet : ViewState() + + data object Error : ViewState() + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAOtpFragment.kt b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAOtpFragment.kt new file mode 100644 index 0000000000..4e690067e9 --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAOtpFragment.kt @@ -0,0 +1,246 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.abha + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.LocalOverscrollConfiguration +import androidx.compose.foundation.background +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.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.viewModels +import com.navi.analytics.utils.NaviTrackEvent +import com.navi.base.model.AnalyticsEvent +import com.navi.base.model.CtaData +import com.navi.base.model.CtaType +import com.navi.base.model.LineItem +import com.navi.base.model.NaviClickAction +import com.navi.base.utils.isNotNull +import com.navi.base.utils.orFalse +import com.navi.base.utils.orZero +import com.navi.common.ui.errorview.FullScreenErrorComposeView +import com.navi.common.ui.fragment.BaseFragment +import com.navi.common.upi.NAME +import com.navi.insurance.R +import com.navi.insurance.analytics.InsuranceAnalyticsHandler +import com.navi.insurance.analytics.NaviInsuranceAnalytics +import com.navi.insurance.models.request.LinkAbhaRequest +import com.navi.insurance.navigator.NaviInsuranceDeeplinkNavigator +import com.navi.insurance.util.AADHAR_NUMBER_EXTRA +import com.navi.insurance.util.ABHA_NUMBER_EXTRA +import com.navi.insurance.util.ASSET_ID_EXTRA +import com.navi.insurance.util.Constants +import com.navi.insurance.util.HEALTH_ASSET_ID_EXTRA +import com.navi.insurance.util.MOBILE_NUMBER_EXTRA +import com.navi.insurance.util.OTP_VALUE_EXTRA +import com.navi.insurance.util.PAGE_SOURCE +import com.navi.insurance.util.POLICY_ID_EXTRA +import com.navi.insurance.util.PROPOSER_RELATIONSHIP_EXTRA +import com.navi.insurance.util.TXN_ID_EXTRA +import com.navi.insurance.util.isTrue +import com.navi.insurance.util.launchHelpCenter +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.GenericComposableWidgetFactory +import com.navi.naviwidgets.composewidget.model.OTPWidgetData +import com.navi.naviwidgets.extensions.ShowShimmer +import com.navi.naviwidgets.models.GenericWidgetDataInfo +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@AndroidEntryPoint +class ABHAOtpFragment : BaseFragment(), WidgetCallback { + private val viewModel by viewModels() + + @Inject lateinit var analyticsHandler: InsuranceAnalyticsHandler + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val composeView = ComposeView(requireContext()).apply { setContent { AbhaAadharScreen() } } + sendLandingAnalyticEvent() + return composeView + } + + private fun sendLandingAnalyticEvent() { + val analyticsEvent = AnalyticsEvent(name = NaviInsuranceAnalytics.GI_ABHA_OTP_SCREEN_LANDS) + analyticsHandler.sendEvent(analyticsEvent, screenName) + } + + override fun onCreate(savedInstanceState: Bundle?) { + viewModel.setPolicyId(arguments?.getString(POLICY_ID_EXTRA)) + viewModel.setPageSource(arguments?.getString(PAGE_SOURCE)) + viewModel.setLinkAbhaRequest(getAbhaLinkRequest()) + viewModel.fetchOTPResponse() + super.onCreate(savedInstanceState) + } + + private fun getAbhaLinkRequest(): LinkAbhaRequest { + return LinkAbhaRequest( + assetId = arguments?.getString(ASSET_ID_EXTRA), + healthAssetId = arguments?.getString(HEALTH_ASSET_ID_EXTRA), + name = arguments?.getString(NAME), + proposerRelationship = arguments?.getString(PROPOSER_RELATIONSHIP_EXTRA), + abhaNumber = arguments?.getString(ABHA_NUMBER_EXTRA), + mobileNumber = arguments?.getString(MOBILE_NUMBER_EXTRA), + aadhaarNumber = arguments?.getString(AADHAR_NUMBER_EXTRA), + txnId = arguments?.getString(TXN_ID_EXTRA), + otpValue = arguments?.getString(OTP_VALUE_EXTRA) + ) + } + + @Composable + fun AbhaAadharScreen() { + val state = viewModel.otpPageResponseFlow.collectAsState() + Box(modifier = Modifier.fillMaxSize().background(Color.White)) { + when { + state.value.isLoading -> { + ShowShimmer( + state.value.isLoading, + requireContext(), + R.layout.navi_insurance_shimmer_tab_layout + ) + } + state.value.hasErrorOccured.isTrue() -> { + FullScreenErrorComposeView(error = null) { viewModel.fetchOTPResponse() } + } + state.value.data.isNotNull() -> { + state.value.data?.let { pageResponse -> AbhaOtpContentScreen(pageResponse) } + } + } + } + } + + @OptIn(ExperimentalFoundationApi::class) + @Composable + fun AbhaOtpContentScreen(data: ABHAPageResponse) { + val widgetCallback: WidgetCallback = this + Scaffold( + modifier = Modifier.fillMaxSize().background(color = Color.White), + topBar = { + data.header?.getOrNull(0)?.let { data -> + GenericComposableWidgetFactory(data = data, this) + } + }, + bottomBar = { data.footer?.getOrNull(0)?.let { data -> PagerFooter(data) } } + ) { paddingValues -> + CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + Box( + modifier = Modifier.fillMaxSize().padding(paddingValues).background(Color.White) + ) { + LazyColumn( + modifier = Modifier.fillMaxWidth().wrapContentHeight().animateContentSize() + ) { + items(data.content?.size.orZero(), key = { it.toString() }) { index -> + val genericWidgetData = data.content?.getOrNull(index) + Column(modifier = Modifier.wrapContentSize()) { + GenericComposableWidgetFactory( + data = genericWidgetData, + widgetCallback = widgetCallback, + onWidgetUpdate = { data, isValid -> + viewModel.updateOtpValidWidgets(data, isValid) + } + ) + } + } + } + } + } + } + } + + @Composable + fun PagerFooter(data: GenericWidgetDataInfo) { + val footerState by viewModel.footerState.collectAsState() + val widgetCallback = this + GenericComposableWidgetFactory(data, state = footerState, widgetCallback = widgetCallback) + } + + override fun onClick(naviClickAction: NaviClickAction, widgetId: String?) { + if (naviClickAction is CtaData) { + naviClickAction.analyticsEventProperties?.let { analyticsEvent -> + NaviTrackEvent.trackEvent(analyticsEvent.name.orEmpty(), analyticsEvent.properties) + } + val bundle = Bundle() + bundle.putParcelable(Constants.PARAMS_EXTRA, naviClickAction) + when (naviClickAction.type) { + CtaType.RELOAD_SCREEN.value -> { + viewModel.fetchOTPResponse() + } + CtaType.GO_BACK.value -> { + activity?.onBackPressed() + } + CtaType.HELP_BOTTOM_SHEET.value -> { + openHelpCenter() + } + CtaType.NAVIGATE_TO_NEW_SCREEN.value -> { + val params = naviClickAction?.parameters?.toMutableList() + viewModel.validOtpWidgets.forEach { widgetDataInfo -> + if ( + widgetDataInfo.value is OTPWidgetData && + widgetDataInfo?.key == OTP_VALUE_EXTRA + ) { + params?.add( + LineItem( + OTP_VALUE_EXTRA, + (widgetDataInfo.value as OTPWidgetData)?.widgetData?.otpValue + ) + ) + } + } + val redirectionCta = naviClickAction.copy(parameters = params) + NaviInsuranceDeeplinkNavigator.navigate( + activity, + ctaData = redirectionCta, + finish = redirectionCta.finish.orFalse(), + clearTask = redirectionCta.clearTask.orFalse(), + clearTop = redirectionCta.clearTop.orFalse() + ) + } + else -> { + NaviInsuranceDeeplinkNavigator.navigate( + activity, + ctaData = naviClickAction, + finish = naviClickAction.finish.orFalse(), + clearTask = naviClickAction.clearTask.orFalse(), + clearTop = naviClickAction.clearTop.orFalse() + ) + } + } + } + } + + private fun openHelpCenter() { + context?.let { context -> launchHelpCenter(context, TAG) } + } + + override val screenName: String = TAG + + companion object { + val TAG = "ABHA_OTP_SCREEN" + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAPageResponse.kt b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAPageResponse.kt new file mode 100644 index 0000000000..990b0a00cb --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAPageResponse.kt @@ -0,0 +1,38 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.abha + +import com.google.gson.annotations.SerializedName +import com.navi.base.model.AnalyticsEvent +import com.navi.insurance.models.response.PageLayoutParams +import com.navi.naviwidgets.models.GenericWidgetDataInfo + +data class ABHAPageResponse( + @SerializedName("pageLayoutParams") val pageLayoutParams: PageLayoutParams? = null, + @SerializedName("header", alternate = ["headerNativeWidget"]) + var header: List? = null, + @SerializedName("content", alternate = ["listOfNativeWidgets"]) + val content: List? = null, + @SerializedName("footer", alternate = ["footerNativeWidget"]) + val footer: List? = null, + @SerializedName("metadata", alternate = ["pageMetaData"]) val metaData: AbhaPageMetaData? = null +) + +data class AbhaPageMetaData( + @SerializedName("analyticsEventProperties") + val analyticsEventProperties: AnalyticsEvent? = null, + @SerializedName("pageValidation") val pageValidation: PageValidation? = null +) + +data class PageValidation(@SerializedName("validationWidgets") val validationWidgets: Int? = null) + +data class AbhaPageResponseState( + val isLoading: Boolean = false, + val hasErrorOccured: Boolean = false, + val data: ABHAPageResponse? = null +) diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHARepository.kt b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHARepository.kt new file mode 100644 index 0000000000..1e72dd7c5e --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHARepository.kt @@ -0,0 +1,32 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.abha + +import com.navi.common.network.models.RepoResult +import com.navi.common.network.retrofit.ResponseCallback +import com.navi.insurance.models.request.LinkAbhaRequest +import com.navi.insurance.network.retrofit.RetrofitService +import javax.inject.Inject + +class ABHARepository @Inject constructor(private val retrofitService: RetrofitService) : + ResponseCallback() { + + suspend fun fetchAbhaResponse( + linkkAbhaRequest: LinkAbhaRequest, + policyId: String?, + pageSource: String? + ): RepoResult { + return apiResponseCallback( + retrofitService.getAbhaScreenResponse( + policyId = policyId.orEmpty(), + pageSource = pageSource.orEmpty(), + abhaLinkRequest = linkkAbhaRequest + ) + ) + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAaadharFragment.kt b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAaadharFragment.kt new file mode 100644 index 0000000000..1a7f91d925 --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAaadharFragment.kt @@ -0,0 +1,258 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.abha + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.LocalOverscrollConfiguration +import androidx.compose.foundation.background +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.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.viewModels +import com.navi.analytics.utils.NaviTrackEvent +import com.navi.base.model.AnalyticsEvent +import com.navi.base.model.CtaData +import com.navi.base.model.CtaType +import com.navi.base.model.LineItem +import com.navi.base.model.NaviClickAction +import com.navi.base.utils.isNotNull +import com.navi.base.utils.orFalse +import com.navi.base.utils.orZero +import com.navi.common.ui.errorview.FullScreenErrorComposeView +import com.navi.common.ui.fragment.BaseFragment +import com.navi.common.upi.NAME +import com.navi.insurance.R as insuranceR +import com.navi.insurance.analytics.InsuranceAnalyticsHandler +import com.navi.insurance.analytics.NaviInsuranceAnalytics +import com.navi.insurance.models.request.LinkAbhaRequest +import com.navi.insurance.navigator.NaviInsuranceDeeplinkNavigator +import com.navi.insurance.util.AADHAR_NUMBER_EXTRA +import com.navi.insurance.util.ABHA_NUMBER_EXTRA +import com.navi.insurance.util.ASSET_ID_EXTRA +import com.navi.insurance.util.Constants +import com.navi.insurance.util.HEALTH_ASSET_ID_EXTRA +import com.navi.insurance.util.MOBILE_NUMBER_EXTRA +import com.navi.insurance.util.OTP_VALUE_EXTRA +import com.navi.insurance.util.PAGE_SOURCE +import com.navi.insurance.util.POLICY_ID_EXTRA +import com.navi.insurance.util.PROPOSER_RELATIONSHIP_EXTRA +import com.navi.insurance.util.TXN_ID_EXTRA +import com.navi.insurance.util.isTrue +import com.navi.insurance.util.launchHelpCenter +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.GenericComposableWidgetFactory +import com.navi.naviwidgets.composewidget.model.EditableTextWidgetData +import com.navi.naviwidgets.extensions.ShowShimmer +import com.navi.naviwidgets.models.GenericWidgetDataInfo +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@AndroidEntryPoint +class ABHAaadharFragment : BaseFragment(), WidgetCallback { + private val viewModel by viewModels() + + @Inject lateinit var analyticsHandler: InsuranceAnalyticsHandler + + override fun onCreate(savedInstanceState: Bundle?) { + viewModel.setPolicyId(arguments?.getString(POLICY_ID_EXTRA)) + viewModel.setPageSource(arguments?.getString(PAGE_SOURCE)) + viewModel.setLinkAbhaRequest(getAbhaLinkRequest()) + viewModel.fetchAadharResponse() + super.onCreate(savedInstanceState) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val composeView = ComposeView(requireContext()).apply { setContent { AbhaAadharScreen() } } + sendLandingAnalyticEvent() + return composeView + } + + private fun sendLandingAnalyticEvent() { + val analyticsEvent = + AnalyticsEvent(name = NaviInsuranceAnalytics.GI_ABHA_AADHAR_SCREEN_LANDS) + analyticsHandler.sendEvent(analyticsEvent, screenName) + } + + private fun getAbhaLinkRequest(): LinkAbhaRequest { + return LinkAbhaRequest( + assetId = arguments?.getString(ASSET_ID_EXTRA), + healthAssetId = arguments?.getString(HEALTH_ASSET_ID_EXTRA), + name = arguments?.getString(NAME), + proposerRelationship = arguments?.getString(PROPOSER_RELATIONSHIP_EXTRA), + abhaNumber = arguments?.getString(ABHA_NUMBER_EXTRA), + mobileNumber = arguments?.getString(MOBILE_NUMBER_EXTRA), + aadhaarNumber = arguments?.getString(AADHAR_NUMBER_EXTRA), + txnId = arguments?.getString(TXN_ID_EXTRA), + otpValue = arguments?.getString(OTP_VALUE_EXTRA) + ) + } + + @Composable + fun AbhaAadharScreen() { + val state = viewModel.aadharPageResponseFlow.collectAsState() + Box(modifier = Modifier.fillMaxSize().background(Color.White)) { + when { + state.value.isLoading -> { + ShowShimmer( + state.value.isLoading, + requireContext(), + insuranceR.layout.navi_insurance_shimmer_tab_layout + ) + } + state.value.hasErrorOccured.isTrue() -> { + FullScreenErrorComposeView(error = null) { viewModel.fetchAadharResponse() } + } + state.value.data.isNotNull() -> { + state.value.data?.let { pageResponse -> AbhaAadharContentScreen(pageResponse) } + } + } + } + } + + @OptIn(ExperimentalFoundationApi::class) + @Composable + fun AbhaAadharContentScreen(data: ABHAPageResponse) { + val widgetCallback: WidgetCallback = this + Scaffold( + modifier = Modifier.fillMaxSize().background(color = Color.White), + topBar = { + data.header?.getOrNull(0)?.let { data -> + GenericComposableWidgetFactory(data = data, this) + } + }, + bottomBar = { data.footer?.getOrNull(0)?.let { data -> PagerFooter(data) } } + ) { paddingValues -> + CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + Box( + modifier = Modifier.fillMaxSize().padding(paddingValues).background(Color.White) + ) { + LazyColumn( + modifier = Modifier.fillMaxWidth().wrapContentHeight().animateContentSize() + ) { + items(data.content?.size.orZero(), key = { it.toString() }) { index -> + val genericWidgetData = data.content?.getOrNull(index) + Column(modifier = Modifier.wrapContentSize()) { + GenericComposableWidgetFactory( + data = genericWidgetData, + widgetCallback = widgetCallback, + onWidgetUpdate = { data, isValid -> + viewModel.updateAadharValidWidgets(data, isValid) + } + ) + } + } + } + } + } + } + } + + override fun onClick(naviClickAction: NaviClickAction, widgetId: String?) { + if (naviClickAction is CtaData) { + naviClickAction.analyticsEventProperties?.let { analyticsEvent -> + NaviTrackEvent.trackEvent(analyticsEvent.name.orEmpty(), analyticsEvent.properties) + } + val bundle = Bundle() + bundle.putParcelable(Constants.PARAMS_EXTRA, naviClickAction) + when (naviClickAction.type) { + CtaType.GO_BACK.value -> { + activity?.onBackPressed() + } + CtaType.HELP_BOTTOM_SHEET.value -> { + openHelpCenter() + } + CtaType.NAVIGATE_TO_NEW_SCREEN.value -> { + val params = naviClickAction?.parameters?.toMutableList() + viewModel.validAadharWidgets.forEach { widgetDataInfo -> + if ( + widgetDataInfo.value is EditableTextWidgetData && + widgetDataInfo?.key == AADHAR_NUMBER_EXTRA + ) { + params?.add( + LineItem( + AADHAR_NUMBER_EXTRA, + (widgetDataInfo.value as EditableTextWidgetData) + ?.widgetData + ?.value + ) + ) + } + + if ( + widgetDataInfo.value is EditableTextWidgetData && + widgetDataInfo?.key == MOBILE_NUMBER_EXTRA + ) { + params?.add( + LineItem( + MOBILE_NUMBER_EXTRA, + (widgetDataInfo.value as EditableTextWidgetData) + ?.widgetData + ?.value + ) + ) + } + } + val redirectionCta = naviClickAction.copy(parameters = params) + NaviInsuranceDeeplinkNavigator.navigate( + activity, + ctaData = redirectionCta, + finish = redirectionCta.finish.orFalse(), + clearTask = redirectionCta.clearTask.orFalse() + ) + } + else -> { + NaviInsuranceDeeplinkNavigator.navigate( + activity, + ctaData = naviClickAction, + finish = naviClickAction.finish.orFalse(), + clearTask = naviClickAction.clearTask.orFalse() + ) + } + } + } + } + + @Composable + fun PagerFooter(data: GenericWidgetDataInfo) { + val footerState by viewModel.footerState.collectAsState() + val widgetCallback = this + GenericComposableWidgetFactory(data, state = footerState, widgetCallback = widgetCallback) + } + + private fun openHelpCenter() { + context?.let { context -> launchHelpCenter(context, TAG) } + } + + override val screenName: String = TAG + + companion object { + val TAG = "ABHA_AADHAR_SCREEN" + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/abha/composables/AbhaShimmer.kt b/android/navi-insurance/src/main/java/com/navi/insurance/abha/composables/AbhaShimmer.kt new file mode 100644 index 0000000000..cd0c33b8d2 --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/abha/composables/AbhaShimmer.kt @@ -0,0 +1,92 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.abha.composables + +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.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import com.navi.uitron.utils.setShimmerEffect + +@Composable +fun AbhaShimmer() { + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + Box( + modifier = + Modifier.padding(vertical = 16.dp, horizontal = 16.dp) + .clip(RoundedCornerShape(4.dp)) + .setShimmerEffect(true) + .width(24.dp) + .height(24.dp) + ) + }, + bottomBar = { + Box( + modifier = + Modifier.clip(RoundedCornerShape(4.dp)) + .setShimmerEffect(true) + .height(64.dp) + .fillMaxWidth() + ) + } + ) { + Column(modifier = Modifier.padding(it)) { + Box( + modifier = + Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp) + .clip(RoundedCornerShape(4.dp)) + .setShimmerEffect(true) + .width(180.dp) + .height(28.dp) + ) + Box( + modifier = + Modifier.padding(top = 8.dp, start = 16.dp, end = 16.dp) + .clip(RoundedCornerShape(4.dp)) + .setShimmerEffect(true) + .fillMaxWidth() + .height(54.dp) + ) + Box( + modifier = + Modifier.padding(top = 20.dp, start = 16.dp, end = 16.dp) + .clip(RoundedCornerShape(4.dp)) + .setShimmerEffect(true) + .fillMaxWidth() + .height(129.dp) + ) + Box( + modifier = + Modifier.padding(top = 32.dp, start = 16.dp, end = 16.dp) + .clip(RoundedCornerShape(4.dp)) + .setShimmerEffect(true) + .width(148.dp) + .height(24.dp) + ) + Box( + modifier = + Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp) + .clip(RoundedCornerShape(4.dp)) + .setShimmerEffect(true) + .fillMaxWidth() + .height(78.dp) + ) + } + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/analytics/InsuranceAnalyticsConstants.kt b/android/navi-insurance/src/main/java/com/navi/insurance/analytics/InsuranceAnalyticsConstants.kt index bdb1bcd5df..29ae49a817 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/analytics/InsuranceAnalyticsConstants.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/analytics/InsuranceAnalyticsConstants.kt @@ -620,6 +620,7 @@ object InsuranceAnalyticsConstants { // Renewals const val INSURANCE_CONTAINER = "insurance_container" + const val ABHA_CONTAINER = "abha_container" const val KYC_ACTIVITY = "kyc_activity" const val HI_ar2_sum_insured_base_amount_continue = "HI_ar2_sum_insured_base_amount_continue" const val PROPERTY_SELECTED_AMOUNT = "selectedAmount" @@ -729,7 +730,7 @@ object InsuranceAnalyticsConstants { const val HI_TELE_MER_SCREEN_LAND = "hi_tele_mer_screen_land" const val QUOTE_ID = "quote_id" const val APPLICATION_TYPE = "application_type" - + const val HI_ABHA_PAGE_LANDS = "hi_pp_abha_number_page_init" // Visit Events Intrumentation const val HI_VISIT_SDK_INIT = "hi_visit_sdk_init" const val HI_VISIT_SDK_ERROR_EVENT = "hi_visit_sdk_error_event" diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/analytics/NaviInsuranceAnalytics.kt b/android/navi-insurance/src/main/java/com/navi/insurance/analytics/NaviInsuranceAnalytics.kt index 9ba64a2380..fd6553f179 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/analytics/NaviInsuranceAnalytics.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/analytics/NaviInsuranceAnalytics.kt @@ -88,6 +88,8 @@ class NaviInsuranceAnalytics private constructor() { const val GI_BENFEXPLAINER_BENEFITPAGE_VIEW = "gi_benfexplainer_benefitpage_view" const val GI_AUTOPAY_SUMMARY_SCREEN = "hi_smart_payment_plan_page_land" const val GI_HEALTH_CARD_SCREEN = "hi_health_cards_screen_lands" + const val GI_ABHA_OTP_SCREEN_LANDS = "hi_abha_otp_screen_lands" + const val GI_ABHA_AADHAR_SCREEN_LANDS = "hi_abha_aadhar_screen_lands" const val BENEFIT_ID = "benefit_id" const val PAGE_ID = "page_name" const val WIDGET_ID = "widget_id" diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/common/util/NavigationHandler.kt b/android/navi-insurance/src/main/java/com/navi/insurance/common/util/NavigationHandler.kt index c0acaba87e..b208169ac6 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/common/util/NavigationHandler.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/common/util/NavigationHandler.kt @@ -238,6 +238,7 @@ class NavigationHandler @Inject constructor(private val uiControllerUtil: UiCont URL_FREE_INSURANCE_ACTIVITY, URL_HI_QUIZ_ACTIVITY, URL_INSURANCE_CONTAINER, + URL_ABHA_CONTAINER, URL_KYC_ACTIVITY, URL_BENEFITS_WEB_VIEW_ACTIVITY, URL_GI_HEALTH_RISK_ACTIVITY, @@ -357,6 +358,7 @@ class NavigationHandler @Inject constructor(private val uiControllerUtil: UiCont const val URL_HI_QUIZ_QUESTION_SCREEN = "hi_quiz_question_screen" const val URL_HI_QUIZ_RESULT_SCREEN = "hi_quiz_result_screen" const val URL_INSURANCE_CONTAINER = "insurance_container" + const val URL_ABHA_CONTAINER = "abha_container" const val URL_KYC_ACTIVITY = "kyc_activity" const val URL_FREE_INSURANCE_PAYMENT = "fi_payment" const val URL_HRA_FI_ACTIVATION = "hra_fi_activation" @@ -398,7 +400,6 @@ class NavigationHandler @Inject constructor(private val uiControllerUtil: UiCont const val URL_HEALTH_CARDS_SCREEN = "policy_health_cards" const val SUMMARY_INSURANCE_TAB = "SUMMARY_INSURANCE_TAB" const val DEFAULT_POLICY_ID = "DEFAULT_POLICY_ID" - const val LANDING_PAGE_VERSION = "landingPageVersion" const val URL_STATIC_DIGITAL_CLAIM = "static_digital_claim" const val TI_EXPLAINER_BOTTOM_SHEET = "trial_explainer_bottomsheet" const val TITLE_WITH_GRID_BOTTOM_SHEET = "title_with_grid_bottomsheet" @@ -413,6 +414,9 @@ class NavigationHandler @Inject constructor(private val uiControllerUtil: UiCont const val TRIAL_INFO_BOTTOM_SHEET = "trial_info_bottom_sheet" const val URL_PAYMENT_STATUS_SCREEN = "payment_status_screen" const val UITRON_BOTTOM_SHEET = "uitron_bottom_sheet" + const val HI_ABHA_SCREEN = "hi_abha_screen" + const val HI_AADHAR_SCREEN = "enter_aadhar_screen" + const val HI_OTP_SCREEN = "enter_otp_screen" const val KYC_STATUS = "gi/kyc_status" const val ICON_WITH_LIST_BOTTOM_SHEET = "icon_with_list_bottomsheet" const val TITLE_WITH_IMAGE_LIST_BOTTOM_SHEET = "title_with_image_list_bottom_sheet" diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/common/util/UiControllerUtil.kt b/android/navi-insurance/src/main/java/com/navi/insurance/common/util/UiControllerUtil.kt index 5d8e7d0619..621e7e2332 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/common/util/UiControllerUtil.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/common/util/UiControllerUtil.kt @@ -14,6 +14,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.navi.base.model.CtaData import com.navi.base.model.LineItem import com.navi.common.video.NaviYoutubeActivity +import com.navi.insurance.abha.ABHAFragment +import com.navi.insurance.abha.ABHAOtpFragment +import com.navi.insurance.abha.ABHAaadharFragment import com.navi.insurance.benefit.explainer.ui.BenefitExplainerFragment import com.navi.insurance.claim.activity.ClaimInfoActivity import com.navi.insurance.claim.activity.NewClaimActivity @@ -92,6 +95,7 @@ import com.navi.insurance.free_insurance.ui.FreeInsuranceCalendarFragment import com.navi.insurance.free_insurance.ui.FreeInsuranceLandingFragment import com.navi.insurance.free_insurance.ui.FreeInsurancePaymentFragment import com.navi.insurance.free_insurance.ui.HraFiActivationFragment +import com.navi.insurance.health.activity.AbhaContainerActivity import com.navi.insurance.health.activity.HospitalsActivity import com.navi.insurance.health.activity.InsuranceContainerActivity import com.navi.insurance.health.activity.KycActivity @@ -201,6 +205,9 @@ class UiControllerUtil @Inject constructor() { NavigationHandler.URL_PRE_QUOTE_JOURNEY_SCREEN -> PreQuoteJourneyFragment() NavigationHandler.URL_HEALTH_CARDS_SCREEN -> HealthCardsFragment() NavigationHandler.POLICY_DETAILS_SCREEN -> PolicyDetailsFragment() + NavigationHandler.HI_ABHA_SCREEN -> ABHAFragment() + NavigationHandler.HI_AADHAR_SCREEN -> ABHAaadharFragment() + NavigationHandler.HI_OTP_SCREEN -> ABHAOtpFragment() else -> null } } @@ -295,6 +302,9 @@ class UiControllerUtil @Inject constructor() { NavigationHandler.URL_STATIC_DIGITAL_CLAIM -> DigitalClaimFragment.TAG NavigationHandler.URL_PRE_QUOTE_JOURNEY_SCREEN -> PreQuoteJourneyFragment.TAG NavigationHandler.URL_HEALTH_CARDS_SCREEN -> HealthCardsFragment.TAG + NavigationHandler.HI_ABHA_SCREEN -> ABHAFragment.TAG + NavigationHandler.HI_AADHAR_SCREEN -> ABHAaadharFragment.TAG + NavigationHandler.HI_OTP_SCREEN -> ABHAOtpFragment.TAG NavigationHandler.POLICY_DETAILS_SCREEN -> PolicyDetailsFragment.TAG else -> "" } @@ -387,6 +397,7 @@ class UiControllerUtil @Inject constructor() { NavigationHandler.URL_FREE_INSURANCE_ACTIVITY -> FreeInsuranceActivity::class.java NavigationHandler.URL_HI_QUIZ_ACTIVITY -> HiQuizActivity::class.java NavigationHandler.URL_INSURANCE_CONTAINER -> InsuranceContainerActivity::class.java + NavigationHandler.URL_ABHA_CONTAINER -> AbhaContainerActivity::class.java NavigationHandler.URL_KYC_ACTIVITY -> KycActivity::class.java NavigationHandler.URL_YOUTUBE_ACTIVITY -> NaviYoutubeActivity::class.java NavigationHandler.URL_BENEFITS_WEB_VIEW_ACTIVITY -> BenefitWebViewActivity::class.java diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/health/activity/AbhaContainerActivity.kt b/android/navi-insurance/src/main/java/com/navi/insurance/health/activity/AbhaContainerActivity.kt new file mode 100644 index 0000000000..aed36b3138 --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/health/activity/AbhaContainerActivity.kt @@ -0,0 +1,51 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.health.activity + +import android.os.Bundle +import androidx.activity.viewModels +import androidx.databinding.DataBindingUtil +import com.navi.base.model.CtaData +import com.navi.insurance.R +import com.navi.insurance.analytics.InsuranceAnalyticsConstants +import com.navi.insurance.common.GiBaseActivity +import com.navi.insurance.common.listener.PaymentStatusBottomSheetListener +import com.navi.insurance.databinding.ActivityInsuranceContainerBinding +import com.navi.insurance.health.viewmodel.BaseVM +import com.navi.insurance.health.viewmodel.InsuranceContainerActivityVM +import com.navi.insurance.paymentreview.autopayoption.ui.PaymentStatusListener +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class AbhaContainerActivity : + GiBaseActivity(), PaymentStatusBottomSheetListener, PaymentStatusListener { + + private lateinit var binding: ActivityInsuranceContainerBinding + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_insurance_container) + } + + override val screenName: String + get() = InsuranceAnalyticsConstants.ABHA_CONTAINER + + override fun getViewModel(): BaseVM = viewModel + + override fun retryClicked(paymentAmount: Double?) = Unit + + override fun onDismissBottomSheet() = Unit + + override fun setPaymentResult( + resultCode: Int, + isCancelledByUser: Boolean, + ctaData: CtaData?, + bundle: Bundle? + ) = Unit +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/models/request/LinkAbhaRequest.kt b/android/navi-insurance/src/main/java/com/navi/insurance/models/request/LinkAbhaRequest.kt new file mode 100644 index 0000000000..3de965c433 --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/models/request/LinkAbhaRequest.kt @@ -0,0 +1,22 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.models.request + +import com.google.gson.annotations.SerializedName + +data class LinkAbhaRequest( + @SerializedName("assetId") val assetId: String? = null, + @SerializedName("healthAssetId") val healthAssetId: String? = null, + @SerializedName("name") val name: String? = null, + @SerializedName("proposerRelationship") val proposerRelationship: String? = null, + @SerializedName("abhaNumber") val abhaNumber: String? = null, + @SerializedName("mobileNumber") val mobileNumber: String? = null, + @SerializedName("aadhaarNumber") val aadhaarNumber: String? = null, + @SerializedName("txnId") val txnId: String? = null, + @SerializedName("otpValue") val otpValue: String? = null +) diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/navigator/NaviInsuranceDeeplinkNavigator.kt b/android/navi-insurance/src/main/java/com/navi/insurance/navigator/NaviInsuranceDeeplinkNavigator.kt index 847366ab4a..472968c77c 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/navigator/NaviInsuranceDeeplinkNavigator.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/navigator/NaviInsuranceDeeplinkNavigator.kt @@ -116,6 +116,7 @@ import com.navi.insurance.formbase.post_purchase.PostPurchaseFormActivity import com.navi.insurance.formbase.pre_purchase.FormActivity import com.navi.insurance.formbase.renewal.RenewalFormActivity import com.navi.insurance.free_insurance.ui.FreeInsuranceActivity +import com.navi.insurance.health.activity.AbhaContainerActivity import com.navi.insurance.health.activity.BaseActivity.Companion.GI_REQUEST_CODE import com.navi.insurance.health.activity.DashboardActivity import com.navi.insurance.health.activity.HospitalsActivity @@ -187,7 +188,8 @@ object NaviInsuranceDeeplinkNavigator { needsResult: Boolean? = null, requestCode: Int? = null, clearTask: Boolean? = false, - callbackHandler: RequestToCallbackHandler? = null + callbackHandler: RequestToCallbackHandler? = null, + clearTop: Boolean = false ) { val deepLink = ctaData.url var intent: Intent? = null @@ -638,6 +640,10 @@ object NaviInsuranceDeeplinkNavigator { intent = Intent(activity, InsuranceContainerActivity::class.java) bundle.putParcelable(KEY_CTA_DATA, ctaData) } + NavigationHandler.URL_ABHA_CONTAINER -> { + intent = Intent(activity, AbhaContainerActivity::class.java) + bundle.putParcelable(KEY_CTA_DATA, ctaData) + } NavigationHandler.URL_KYC_ACTIVITY -> { intent = Intent(activity, KycActivity::class.java) } @@ -663,6 +669,9 @@ object NaviInsuranceDeeplinkNavigator { Intent.FLAG_ACTIVITY_CLEAR_TOP ) } + if (clearTop) { + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + } if (needsResult.orFalse()) { activity?.startActivityForResult(intent, requestCode.orZero()) } else activity?.startActivity(intent) diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/network/retrofit/RetrofitService.kt b/android/navi-insurance/src/main/java/com/navi/insurance/network/retrofit/RetrofitService.kt index f8466ea41b..f25985e794 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/network/retrofit/RetrofitService.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/network/retrofit/RetrofitService.kt @@ -10,6 +10,7 @@ package com.navi.insurance.network.retrofit import com.google.gson.JsonElement import com.navi.base.model.CtaData import com.navi.common.network.models.GenericResponse +import com.navi.insurance.abha.ABHAPageResponse import com.navi.insurance.common.models.ActionResultResponse import com.navi.insurance.common.models.CallbackRequestBody import com.navi.insurance.common.models.ChatRequest @@ -65,6 +66,7 @@ import com.navi.insurance.models.request.FormNextPageRequest import com.navi.insurance.models.request.FormPreviousPageRequest import com.navi.insurance.models.request.InitiateInstallmentPaymentRequest import com.navi.insurance.models.request.InitiatePaymentRequest +import com.navi.insurance.models.request.LinkAbhaRequest import com.navi.insurance.models.request.MandateRegistrationRequest import com.navi.insurance.models.request.OfferRequest import com.navi.insurance.models.request.PaymentHashRequest @@ -1368,4 +1370,11 @@ interface RetrofitService { @Query("screenName") screenName: String, @Body callbackRequestBody: CallbackRequestBody? ): Response> + + @POST("/gi/policy/{policyId}/abha-link") + suspend fun getAbhaScreenResponse( + @Path("policyId") policyId: String, + @Query("pageSource") pageSource: String, + @Body abhaLinkRequest: LinkAbhaRequest + ): Response> } diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/util/IntentConstants.kt b/android/navi-insurance/src/main/java/com/navi/insurance/util/IntentConstants.kt index 705e7672d9..b21af83674 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/util/IntentConstants.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/util/IntentConstants.kt @@ -58,3 +58,11 @@ const val REDIRECTION_CTA = "redirectionCta" const val RELOAD_PARENT_SCREEN = "reloadParentScreen" const val PAYMENT_ORDER_DETAIL = "PAYMENT_ORDER_DETAIL" const val PAGE_SOURCE = "pageSource" +const val ASSET_ID_EXTRA = "assetId" +const val HEALTH_ASSET_ID_EXTRA = "healthAssetId" +const val PROPOSER_RELATIONSHIP_EXTRA = "proposerRelationship" +const val ABHA_NUMBER_EXTRA = "abhaNumber" +const val MOBILE_NUMBER_EXTRA = "mobileNumber" +const val AADHAR_NUMBER_EXTRA = "aadhaarNumber" +const val TXN_ID_EXTRA = "txnId" +const val OTP_VALUE_EXTRA = "otpValue" diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/WidgetDataDeserializer.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/WidgetDataDeserializer.kt index 67ad32005d..ba8aa58b49 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/WidgetDataDeserializer.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/WidgetDataDeserializer.kt @@ -12,8 +12,14 @@ import com.navi.insurance.models.response.BenefitExplainerTabsData import com.navi.insurance.models.response.BenefitExplainerTabsList import com.navi.insurance.models.response.BenefitExplainerTabsWidgetList import com.navi.insurance.models.response.BenefitExplainerWidgetBodyData +import com.navi.naviwidgets.composewidget.model.AbhaWidget import com.navi.naviwidgets.composewidget.model.CardWithFlipAnimationWidget +import com.navi.naviwidgets.composewidget.model.CenteredIconTextWidget +import com.navi.naviwidgets.composewidget.model.EditableTextWidgetData +import com.navi.naviwidgets.composewidget.model.OTPWidgetData import com.navi.naviwidgets.composewidget.model.PolicyHealthCardWidget +import com.navi.naviwidgets.composewidget.model.RedirectionCtaWidgetData +import com.navi.naviwidgets.composewidget.model.SelectableTextWidgetData import com.navi.naviwidgets.models.* import com.navi.naviwidgets.models.CentreTitleSubtitleWidgetData import com.navi.naviwidgets.models.FooterWithCardAndSnackbarWidgetData @@ -435,6 +441,24 @@ class WidgetDataDeserializer : JsonDeserializer { FooterSectionWidgetData::class.java ) } + WidgetTypes.ABHA_WIDGET.value -> { + Gson().fromJson(jsonObject, AbhaWidget::class.java) + } + WidgetTypes.CENTERED_ICON_TEXT_WIDGET.value -> { + Gson().fromJson(jsonObject, CenteredIconTextWidget::class.java) + } + WidgetTypes.SELECTABLE_TEXT_WIDGET.value -> { + Gson().fromJson(jsonObject, SelectableTextWidgetData::class.java) + } + WidgetTypes.OTP_WIDGET.value -> { + Gson().fromJson(jsonObject, OTPWidgetData::class.java) + } + WidgetTypes.EDITABLE_TEXT_WIDGET.value -> { + Gson().fromJson(jsonObject, EditableTextWidgetData::class.java) + } + WidgetTypes.REDIRECTION_CTA.value -> { + Gson().fromJson(jsonObject, RedirectionCtaWidgetData::class.java) + } WidgetTypes.CARD_WITH_LIST_ITEMS_WIDGET.value -> { Gson().fromJson(jsonObject, CardWithListItemsWidgetData::class.java) } diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/WidgetTypes.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/WidgetTypes.kt index 5aca60c6a2..e64b28bb6a 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/WidgetTypes.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/WidgetTypes.kt @@ -122,6 +122,13 @@ enum class WidgetTypes(val value: String) { TITLE_WITH_GRID_AND_FOOTER_BUTTON_WIDGET("TITLE_WITH_GRID_AND_FOOTER_BUTTON_WIDGET"), LEFT_RIGHT_TITLE_LIST_WIDGET("LEFT_RIGHT_TITLE_LIST_WIDGET"), TOAST_WIDGET_LIST("TOAST_WIDGET_LIST"), + FOOTER_SECTION_TEMPLATE("FOOTER_SECTION_TEMPLATE"), + ABHA_WIDGET("ABHA_WIDGET"), + CENTERED_ICON_TEXT_WIDGET("CENTERED_ICON_TEXT_WIDGET"), + SELECTABLE_TEXT_WIDGET("SELECTABLE_TEXT_WIDGET"), + EDITABLE_TEXT_WIDGET("EDITABLE_TEXT_WIDGET"), + OTP_WIDGET("OTP_WIDGET"), + REDIRECTION_CTA("REDIRECTION_CTA"), TITLE_CONTENT_IMAGE_FOOTER_WITH_CTA("TITLE_CONTENT_IMAGE_FOOTER_WITH_CTA"), HORIZONTAL_CARD_CAROUSEL_WIDGET("HORIZONTAL_CARD_CAROUSEL_WIDGET"), TITLE_CARD_CTA_WIDGET("TITLE_CARD_CTA_WIDGET"), @@ -129,6 +136,5 @@ enum class WidgetTypes(val value: String) { CARD_WITH_LIST_ITEMS_WIDGET("CARD_WITH_LIST_ITEMS_WIDGET"), TABLE_WITH_TOOLTIP_AND_CTA_WIDGET("TABLE_WITH_TOOLTIP_AND_CTA_WIDGET"), GRID_WITH_ASSET_CARD_WIDGET("GRID_WITH_ASSET_CARD_WIDGET"), - FOOTER_SECTION_TEMPLATE("FOOTER_SECTION_TEMPLATE"), - TITLE_WITH_BUTTON_AND_ICON_WIDGET("TITLE_WITH_BUTTON_AND_ICON_WIDGET"), + TITLE_WITH_BUTTON_AND_ICON_WIDGET("TITLE_WITH_BUTTON_AND_ICON_WIDGET") } diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/GenericComposableWidgetFactory.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/GenericComposableWidgetFactory.kt index e98e3770a5..9633c846c5 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/GenericComposableWidgetFactory.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/GenericComposableWidgetFactory.kt @@ -14,11 +14,20 @@ import com.navi.base.utils.orFalse import com.navi.naviwidgets.ToolbarWidget import com.navi.naviwidgets.WidgetTypes import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.model.AbhaWidget import com.navi.naviwidgets.composewidget.model.CardWithFlipAnimationWidget +import com.navi.naviwidgets.composewidget.model.CenteredIconTextWidget +import com.navi.naviwidgets.composewidget.model.EditableTextWidgetData +import com.navi.naviwidgets.composewidget.model.OTPWidgetData import com.navi.naviwidgets.composewidget.model.PolicyHealthCardWidget +import com.navi.naviwidgets.composewidget.model.RedirectionCtaWidgetData +import com.navi.naviwidgets.composewidget.model.SelectableTextWidgetData +import com.navi.naviwidgets.composewidget.widgets.AbhaWidgetComposable import com.navi.naviwidgets.composewidget.widgets.CardWithFlipAnimationComposable import com.navi.naviwidgets.composewidget.widgets.CardWithListItemsWidgetComposable +import com.navi.naviwidgets.composewidget.widgets.CenteredIconTextWidgetComposable import com.navi.naviwidgets.composewidget.widgets.DismissableToastWidgetData +import com.navi.naviwidgets.composewidget.widgets.EditableTextWidgetComposable import com.navi.naviwidgets.composewidget.widgets.FooterSectionWidgetComposable import com.navi.naviwidgets.composewidget.widgets.FooterWithCardAndSnackBarWidgetComposable import com.navi.naviwidgets.composewidget.widgets.GridWithAssetCardWidgetComposable @@ -28,11 +37,15 @@ import com.navi.naviwidgets.composewidget.widgets.HorizontalGridWidgetComposable import com.navi.naviwidgets.composewidget.widgets.ImageTitleCardWidgetComposable import com.navi.naviwidgets.composewidget.widgets.InfoCardWithTitleSubTitleButtonWidgetComposable import com.navi.naviwidgets.composewidget.widgets.LeftRightTitleListWidgetComposable +import com.navi.naviwidgets.composewidget.widgets.OTPWidgetComposable import com.navi.naviwidgets.composewidget.widgets.PolicyCardCarouselWidgetComposable import com.navi.naviwidgets.composewidget.widgets.PolicyCardWidgetComposable import com.navi.naviwidgets.composewidget.widgets.PolicyDetailsCardV2WidgetData import com.navi.naviwidgets.composewidget.widgets.PolicyHealthCardComposable +import com.navi.naviwidgets.composewidget.widgets.RedirectionCtaWidgetComposable +import com.navi.naviwidgets.composewidget.widgets.SelectableTextWidgetComposable import com.navi.naviwidgets.composewidget.widgets.SeparatorWidgetComposable +import com.navi.naviwidgets.composewidget.widgets.SingleImageWidgetComposable import com.navi.naviwidgets.composewidget.widgets.TableWithTooltipAndCtaWidgetComposable import com.navi.naviwidgets.composewidget.widgets.TitleCardCtaWidgetComposable import com.navi.naviwidgets.composewidget.widgets.TitleContentFooterCardWidgetComposable @@ -64,6 +77,7 @@ import com.navi.naviwidgets.models.response.LeftRightTitleListWidgetData import com.navi.naviwidgets.models.response.PolicyCardCarouselWidgetData import com.navi.naviwidgets.models.response.PolicyCardWidgetData import com.navi.naviwidgets.models.response.PolicyDetailsCardV2WidgetData +import com.navi.naviwidgets.models.response.SingleImageWidget import com.navi.naviwidgets.models.response.TableWithTooltipAndCtaWidgetData import com.navi.naviwidgets.models.response.TitleCardCtaWidgetData import com.navi.naviwidgets.models.response.TitleWidget @@ -79,7 +93,8 @@ fun GenericComposableWidgetFactory( data: GenericWidgetDataInfo?, widgetCallback: WidgetCallback? = null, isFirstItemVisible: Boolean = false, - onWidgetUpdate: (updatedData: GenericWidgetDataInfo) -> Unit = {} + onWidgetUpdate: (updatedData: GenericWidgetDataInfo, isValid: Boolean) -> Unit = { _, _ -> }, + state: String = FooterButtonState.ENABLED.name ) { LaunchedEffect(key1 = data.toString()) { data?.widgetAnalyticsProperties?.let { @@ -120,7 +135,7 @@ fun GenericComposableWidgetFactory( WidgetTypes.FOOTER_WITH_CARD_AND_SNACKBAR_WIDGET.value -> { FooterWithCardAndSnackBarWidgetComposable( (data as? FooterWithCardAndSnackbarWidgetData), - state = FooterButtonState.ENABLED.name, + state = state, widgetCallback ) } @@ -233,6 +248,35 @@ fun GenericComposableWidgetFactory( onWidgetUpdate ) } + WidgetTypes.ABHA_WIDGET.value -> { + AbhaWidgetComposable(data as? AbhaWidget, widgetCallback) + } + WidgetTypes.CENTERED_ICON_TEXT_WIDGET.value -> { + CenteredIconTextWidgetComposable(data as? CenteredIconTextWidget, widgetCallback) + } + WidgetTypes.SINGLE_IMAGE_WIDGET.value -> { + SingleImageWidgetComposable(data as? SingleImageWidget, widgetCallback) + } + WidgetTypes.OTP_WIDGET.value -> { + OTPWidgetComposable(data as? OTPWidgetData, widgetCallback, onWidgetUpdate) + } + WidgetTypes.EDITABLE_TEXT_WIDGET.value -> { + EditableTextWidgetComposable( + data as? EditableTextWidgetData, + widgetCallback, + onWidgetUpdate + ) + } + WidgetTypes.SELECTABLE_TEXT_WIDGET.value -> { + SelectableTextWidgetComposable( + data as? SelectableTextWidgetData, + widgetCallback, + onWidgetUpdate + ) + } + WidgetTypes.REDIRECTION_CTA.value -> { + RedirectionCtaWidgetComposable(data as? RedirectionCtaWidgetData, widgetCallback) + } WidgetTypes.TITLE_WITH_BUTTON_AND_ICON_WIDGET.value -> { TitleWithButtonAndIconWidgetComposable( (data as? TitleWithButtonAndIconWidgetData), diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/ABHAWidget.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/ABHAWidget.kt new file mode 100644 index 0000000000..4ae25cff13 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/ABHAWidget.kt @@ -0,0 +1,73 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.model + +import com.navi.base.model.AnalyticsEvent +import com.navi.base.model.CtaData +import com.navi.naviwidgets.models.GenericWidgetDataInfo +import com.navi.naviwidgets.models.response.ImageFieldData +import com.navi.naviwidgets.models.response.TextFieldData + +data class AbhaWidget(val widgetData: ABHAWidgetData? = null) : + GenericWidgetDataInfo( + widgetId = WIDGET_ID, + widgetNameForBaseAdapter = WIDGET_ID, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + + companion object { + const val WIDGET_ID = "ABHA_WIDGET" + } + + data class ABHAWidgetData( + val title: TextFieldData? = null, + val items: List? = null + ) { + data class ABHAItemData( + val memberName: TextFieldData? = null, + val memberRelation: TextFieldData? = null, + val memberImage: ImageFieldData? = null, + val linkText: TextFieldData? = null, + var isAbhaLinked: Boolean? = null, + val abhaData: ABHAData? = null, + val abhaBottomSheetData: ABHABottomSheetData? = null, + ) { + data class ABHAData( + val title: TextFieldData? = null, + var abhaNumber: TextFieldData? = null + ) + + data class ABHABottomSheetData( + val title: TextFieldData? = null, + val endIcon: ImageFieldData? = null, + val inputHint: TextFieldData? = null, + val inputText: TextFieldData? = null, + val errorText: TextFieldData? = null, + val forgotAbhaText: TextFieldData? = null, + val footerText: TextFieldData? = null, + val footerButton: LottieButtonData? = null, + val assetId: String? = null, + val healthAssetId: String? = null, + val policyId: String? = null, + val landingEvent: AnalyticsEvent? = null + ) + + data class LottieButtonData( + val title: TextFieldData? = null, + val backgroundColor: String? = null, + val cta: CtaData? = null, + val lottieUrl: String? = null, + val selectedColor: String? = null, + val unselectedColor: String? = null + ) + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/CenteredIconTextWidget.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/CenteredIconTextWidget.kt new file mode 100644 index 0000000000..694b034241 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/CenteredIconTextWidget.kt @@ -0,0 +1,36 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.model + +import com.navi.naviwidgets.models.GenericWidgetDataInfo +import com.navi.naviwidgets.models.response.ImageFieldData +import com.navi.naviwidgets.models.response.TextFieldData + +class CenteredIconTextWidget(val widgetData: CenteredIconTextWidgetData? = null) : + GenericWidgetDataInfo( + widgetId = WIDGET_ID, + widgetNameForBaseAdapter = WIDGET_ID, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + + companion object { + const val WIDGET_ID = "CENTERED_ICON_TEXT_WIDGET" + } + + data class CenteredIconTextWidgetData( + val startIcon: ImageFieldData? = null, + val text: TextFieldData? = null, + val endIcon: ImageFieldData? = null, + val textPadding: TextPadding? = null + ) { + data class TextPadding(val start: Int? = 0, val end: Int? = 0) + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/EditableTextWidgetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/EditableTextWidgetData.kt new file mode 100644 index 0000000000..525dd8455f --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/EditableTextWidgetData.kt @@ -0,0 +1,40 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.model + +import com.google.gson.annotations.SerializedName +import com.navi.naviwidgets.models.GenericWidgetDataInfo +import com.navi.naviwidgets.models.response.TextFieldData + +data class EditableTextWidgetData(val widgetData: EditableTextWidgetContentData? = null) : + GenericWidgetDataInfo( + widgetId = WIDGET_ID, + widgetNameForBaseAdapter = WIDGET_ID, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + companion object { + const val WIDGET_ID = "EDITABLE_TEXT_WIDGET" + } +} + +data class EditableTextWidgetContentData( + @SerializedName("hintText") val hintText: TextFieldData? = null, + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("fixedTitle") val fixedTitle: TextFieldData? = null, + @SerializedName("textRules") val textRules: TextRules? = null, + @SerializedName("value") val value: String? = null, + @SerializedName("id") val id: String? = null +) + +data class TextRules( + @SerializedName("inputType") val inputType: String? = null, + @SerializedName("charLimit") val charLimit: Int? = null +) diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/OTPWidgetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/OTPWidgetData.kt new file mode 100644 index 0000000000..2c480baff2 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/OTPWidgetData.kt @@ -0,0 +1,32 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.model + +import com.google.gson.annotations.SerializedName +import com.navi.naviwidgets.models.GenericWidgetDataInfo + +data class OTPWidgetData(val widgetData: OTPWidgetContentData? = null) : + GenericWidgetDataInfo( + widgetId = WIDGET_ID, + widgetNameForBaseAdapter = WIDGET_ID, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + companion object { + const val WIDGET_ID = "SELECTABLE_TEXT_WIDGET" + } +} + +data class OTPWidgetContentData( + @SerializedName("textRules") val textRules: TextRules? = null, + @SerializedName("newOtpDelay") val newOtpDelay: Int? = null, + @SerializedName("otpValue") val otpValue: String? = null, + @SerializedName("id") val id: String? = null +) diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/RedirectionCtaWidgetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/RedirectionCtaWidgetData.kt new file mode 100644 index 0000000000..3591665a30 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/RedirectionCtaWidgetData.kt @@ -0,0 +1,28 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.model + +import com.google.gson.annotations.SerializedName +import com.navi.base.model.CtaData +import com.navi.naviwidgets.models.GenericWidgetDataInfo + +data class RedirectionCtaWidgetData(val widgetData: RedirectionCtaWidgetDataBody? = null) : + GenericWidgetDataInfo( + widgetId = WIDGET_ID, + widgetNameForBaseAdapter = WIDGET_ID, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + companion object { + const val WIDGET_ID = "REDIRECTION_CTA" + } +} + +data class RedirectionCtaWidgetDataBody(@SerializedName("cta") val cta: CtaData? = null) diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/SelectableTextWidgetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/SelectableTextWidgetData.kt new file mode 100644 index 0000000000..2d4a7283e3 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/SelectableTextWidgetData.kt @@ -0,0 +1,37 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.model + +import com.google.gson.annotations.SerializedName +import com.navi.naviwidgets.models.GenericWidgetDataInfo +import com.navi.naviwidgets.models.response.TextFieldData + +data class SelectableTextWidgetData(val widgetData: SelectableTextWidgetContentData? = null) : + GenericWidgetDataInfo( + widgetId = WIDGET_ID, + widgetNameForBaseAdapter = WIDGET_ID, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + companion object { + const val WIDGET_ID = "SELECTABLE_TEXT_WIDGET" + } +} + +data class SelectableTextWidgetContentData( + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("checkboxData") val checkboxData: CheckBoxData? = null, + @SerializedName("value") val value: Boolean? = null +) + +data class CheckBoxData( + @SerializedName("isChecked") val isChecked: Boolean? = null, + @SerializedName("backgroundColor") val backgroundColor: String? = null +) diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/reusable/AppColors.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/reusable/AppColors.kt index 1e4663b453..afb462c24d 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/reusable/AppColors.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/reusable/AppColors.kt @@ -26,6 +26,7 @@ val midBlue: Color = Color(0xFFEAF2FF) val text_secondary: Color = Color(0xFFA8A8A8) val colorShimmerEffect: Color = Color(0x99E9E7F0) val colorInputFields: Color = Color(0xFFFF0000) +val colorGrey: Color = Color(0xFFEBEBEB) const val colorTextTertiaryHex = "#6B6B6B" const val colorTextPrimaryHex = "#191919" const val colorTextSecondaryHex = "#444444" diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/reusable/CustomCheckBox.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/reusable/CustomCheckBox.kt new file mode 100644 index 0000000000..2ad952c5d5 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/reusable/CustomCheckBox.kt @@ -0,0 +1,63 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.reusable + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.selection.toggleable +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +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 +fun CustomCheckBox( + modifier: Modifier = Modifier, + checked: Boolean = false, + onCheckedChange: (Boolean) -> Unit = {}, + selectedColor: Color = colorHIBlue +) { + Card( + modifier = modifier, + elevation = 0.dp, + shape = RoundedCornerShape(4.dp), + backgroundColor = whiteColor, + border = + BorderStroke( + width = 1.dp, + color = checked.let { if (it) selectedColor else colorTextTertiary } + ) + ) { + Box( + modifier = + Modifier.background( + if (checked) selectedColor else whiteColor, + ) + .size(20.dp) + .toggleable(value = checked, onValueChange = { onCheckedChange(it) }), + contentAlignment = Alignment.Center + ) { + if (checked) { + Icon( + Icons.Default.Check, + contentDescription = "Checkbox selected", + tint = Color.White, + modifier = Modifier.size(16.dp) + ) + } + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/AbhaWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/AbhaWidgetComposable.kt new file mode 100644 index 0000000000..cd83076957 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/AbhaWidgetComposable.kt @@ -0,0 +1,113 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.Row +import androidx.compose.foundation.layout.fillMaxWidth +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.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.model.AbhaWidget +import com.navi.naviwidgets.composewidget.reusable.colorCTAPrimary +import com.navi.naviwidgets.composewidget.reusable.colorGrey +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.setWidgetLayoutParams +import com.navi.naviwidgets.utils.DottedShape + +@Composable +fun AbhaWidgetComposable(abhaWidget: AbhaWidget?, widgetCallback: WidgetCallback? = null) { + val widgetData = abhaWidget?.widgetData + setWidgetLayoutParams(widgetLayoutParams = abhaWidget?.widgetLayoutParams) { + Column { + NaviTextWidgetized(textFieldData = widgetData?.title) + widgetData?.items?.forEach { + Column( + modifier = + Modifier.padding(top = 16.dp) + .fillMaxWidth() + .border(1.dp, colorGrey, RoundedCornerShape(4.dp)) + .background(color = Color.White, shape = RoundedCornerShape(4.dp)) + ) { + Row( + modifier = Modifier.fillMaxWidth().padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(modifier = Modifier.weight(1f)) { + NaviImage( + modifier = Modifier.size(40.dp), + imageFieldData = it?.memberImage + ) + Column(modifier = Modifier.padding(start = 16.dp)) { + NaviTextWidgetized(textFieldData = it?.memberName) + NaviTextWidgetized( + textFieldData = it?.memberRelation, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + if (it?.isAbhaLinked != true) + NaviTextWidgetized( + modifier = + Modifier.background( + color = colorCTAPrimary, + shape = RoundedCornerShape(4.dp) + ) + .clickable { + it?.linkText?.cta?.let { it1 -> + widgetCallback?.onClick(it1) + } + } + .padding( + start = 8.dp, + top = 4.dp, + bottom = 4.dp, + end = 4.dp + ), + textFieldData = it?.linkText + ) + } + if (it?.isAbhaLinked == true) { + Box( + Modifier.height(1.dp) + .fillMaxWidth() + .background(colorGrey, shape = DottedShape(step = 10.dp)) + ) + Row( + modifier = Modifier.fillMaxWidth().padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + NaviTextWidgetized(textFieldData = it.abhaData?.title) + NaviTextWidgetized( + modifier = + Modifier.clickable { + it.abhaData?.abhaNumber?.cta?.let { it1 -> + widgetCallback?.onClick(it1) + } + }, + textFieldData = it.abhaData?.abhaNumber + ) + } + } + } + } + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CenteredIconTextWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CenteredIconTextWidgetComposable.kt new file mode 100644 index 0000000000..a2dccc2940 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CenteredIconTextWidgetComposable.kt @@ -0,0 +1,61 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.navi.base.utils.orZero +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.model.CenteredIconTextWidget +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.setWidgetLayoutParams + +@Composable +fun CenteredIconTextWidgetComposable( + centeredIconTextWidget: CenteredIconTextWidget?, + widgetCallback: WidgetCallback? = null +) { + val widgetData = centeredIconTextWidget?.widgetData + setWidgetLayoutParams(widgetLayoutParams = centeredIconTextWidget?.widgetLayoutParams) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + NaviImage( + modifier = + Modifier.height(widgetData?.startIcon?.iconHeight.orZero().dp) + .width(widgetData?.startIcon?.iconWidth.orZero().dp), + imageFieldData = widgetData?.startIcon + ) + NaviTextWidgetized( + modifier = + Modifier.padding( + start = widgetData?.textPadding?.start.orZero().dp, + end = widgetData?.textPadding?.end.orZero().dp + ), + textFieldData = widgetData?.text + ) + NaviImage( + modifier = + Modifier.height(widgetData?.endIcon?.iconHeight.orZero().dp) + .width(widgetData?.endIcon?.iconWidth.orZero().dp), + imageFieldData = widgetData?.endIcon + ) + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/EditableTextWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/EditableTextWidgetComposable.kt new file mode 100644 index 0000000000..df732e9ac1 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/EditableTextWidgetComposable.kt @@ -0,0 +1,111 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.sp +import com.navi.base.utils.orZero +import com.navi.design.font.FontWeightEnum +import com.navi.design.theme.getFontWeight +import com.navi.design.theme.ttComposeFontFamily +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.model.EditableTextWidgetData +import com.navi.naviwidgets.composewidget.reusable.colorBorderAlt +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.getKeyBoardType +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.naviwidgets.extensions.setWidgetLayoutParams + +@Composable +fun EditableTextWidgetComposable( + editableTextData: EditableTextWidgetData?, + widgetCallback: WidgetCallback? = null, + onWidgetUpdate: (updatedData: EditableTextWidgetData, isValid: Boolean) -> Unit +) { + var textLimit by remember { mutableStateOf(editableTextData?.widgetData?.title?.text ?: "") } + editableTextData?.widgetData?.let { data -> + setWidgetLayoutParams(widgetLayoutParams = editableTextData?.widgetLayoutParams) { + Row( + modifier = Modifier.background(Color.White).fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = textLimit, + onValueChange = { input -> + if (input.length <= (data?.textRules?.charLimit ?: 0)) { + if ( + input.length < textLimit.length || + input.length <= (data?.textRules?.charLimit ?: 0) + ) { + textLimit = input + } + editableTextData + ?.copy( + widgetData = editableTextData?.widgetData?.copy(value = input) + ) + ?.let { + onWidgetUpdate( + it, + (input?.length) == + (editableTextData?.widgetData?.textRules?.charLimit + ?: 0) + ) + } + } + }, + placeholder = { NaviTextWidgetized(data?.hintText) }, + leadingIcon = + if (data?.fixedTitle != null) { + { NaviTextWidgetized(data.fixedTitle) } + } else null, + singleLine = true, + keyboardOptions = + KeyboardOptions.Default.copy( + keyboardType = getKeyBoardType(data?.textRules?.inputType) + ), + colors = + OutlinedTextFieldDefaults.colors( + focusedBorderColor = colorBorderAlt, + unfocusedBorderColor = colorBorderAlt, + cursorColor = Color.Black, + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White, + errorCursorColor = Color.Black + ), + textStyle = + TextStyle( + fontFamily = ttComposeFontFamily, + fontSize = data?.title?.size?.toFloat().orZero().sp, + color = hexToColor(data?.title?.textColor), + fontWeight = + getFontWeight( + FontWeightEnum.valueOf( + data?.title?.font ?: FontWeightEnum.TT_REGULAR.name + ) + ) + ) + ) + } + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/OTPWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/OTPWidgetComposable.kt new file mode 100644 index 0000000000..2f7523bc4b --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/OTPWidgetComposable.kt @@ -0,0 +1,210 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.navi.base.model.CtaData +import com.navi.base.model.CtaType +import com.navi.design.font.FontWeightEnum +import com.navi.design.utils.Constants +import com.navi.design.utils.Constants.REGENERATE_OTP +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.model.OTPWidgetData +import com.navi.naviwidgets.composewidget.reusable.colorBorderAlt +import com.navi.naviwidgets.composewidget.reusable.colorCTAPrimary +import com.navi.naviwidgets.composewidget.reusable.colorCTAPrimaryHex +import com.navi.naviwidgets.composewidget.reusable.colorTextPrimary +import com.navi.naviwidgets.composewidget.reusable.colorTextPrimaryHex +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.getComposeTextStyling +import com.navi.naviwidgets.extensions.setWidgetLayoutParams +import com.navi.naviwidgets.models.response.TextFieldData +import kotlinx.coroutines.delay +import timber.log.Timber + +@Composable +fun OTPWidgetComposable( + otpWidgetData: OTPWidgetData?, + widgetCallback: WidgetCallback? = null, + onWidgetUpdate: (updatedData: OTPWidgetData, isValid: Boolean) -> Unit, +) { + otpWidgetData?.widgetData?.let { data -> + Box(modifier = Modifier.background(Color.White)) { + setWidgetLayoutParams(widgetLayoutParams = otpWidgetData?.widgetLayoutParams) { + Column( + modifier = Modifier.fillMaxWidth().background(Color.White), + horizontalAlignment = Alignment.Start + ) { + var otpValue by remember { mutableStateOf("") } + var timerState by remember { mutableStateOf(data?.newOtpDelay ?: 0) } + + LaunchedEffect(timerState) { + while (timerState > 0) { + delay(1000L) + timerState-- + } + } + + val otpTextData by remember { + derivedStateOf { + TextFieldData( + text = + if (timerState > 0) + Constants.REGENERATE_OTP_TIME + + timerState.toString().padStart(2, '0') + else REGENERATE_OTP, + textColor = + if (timerState > 0) colorCTAPrimaryHex else colorTextPrimaryHex, + size = 12, + fontWeight = 400, + font = + if (timerState > 0) FontWeightEnum.TT_REGULAR.name + else FontWeightEnum.TT_SEMI_BOLD.name, + cta = + if (timerState <= 0) CtaData(type = CtaType.RELOAD_SCREEN.value) + else null, + underline = timerState <= 0 + ) + } + } + + OtpTextField( + otpText = otpValue, + onOtpTextChange = { value, otpInputFilled -> + otpValue = value + onWidgetUpdate( + otpWidgetData.copy(widgetData = data.copy(otpValue = value)), + otpInputFilled + ) + }, + otpCount = data?.textRules?.charLimit ?: 0 + ) + OtpTextData(otpTextData, widgetCallback) + } + } + } + } +} + +@Composable +private fun OtpTextData(otpTextData: TextFieldData, widgetCallback: WidgetCallback?) { + NaviTextWidgetized( + modifier = Modifier.padding(top = 12.dp), + textFieldData = otpTextData, + widgetCallback = widgetCallback + ) +} + +@Composable +fun OtpTextField( + modifier: Modifier = Modifier, + otpText: String, + otpCount: Int = 6, + onOtpTextChange: (String, Boolean) -> Unit +) { + LaunchedEffect(Unit) { + if (otpText.length > otpCount) { + Timber.e("OTP length is greater than the limit") + } + } + + BasicTextField( + modifier = modifier, + value = TextFieldValue(otpText, selection = TextRange(otpText.length)), + onValueChange = { + if (it.text.length <= otpCount) { + onOtpTextChange.invoke(it.text, it.text.length == otpCount) + } + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + decorationBox = { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + repeat(otpCount) { index -> + CharView(index = index, text = otpText) + Spacer(modifier = Modifier.width(8.dp)) + } + } + } + ) +} + +@Composable +private fun CharView(index: Int, text: String) { + val isFocused = text.length == index + val char = + when { + index == text.length -> "-" + index > text.length -> "-" + else -> text[index].toString() + } + Text( + modifier = + Modifier.width(48.dp) + .height(48.dp) + .border( + 1.dp, + when { + isFocused -> colorCTAPrimary + else -> colorBorderAlt + }, + RoundedCornerShape(4.dp) + ) + .padding(2.dp) + .wrapContentHeight(Alignment.CenterVertically), + text = char, + style = + getComposeTextStyling( + TextFieldData( + textColor = colorTextPrimaryHex, + size = 16, + font = FontWeightEnum.TT_REGULAR.name + ) + ), + color = + if (isFocused) { + colorTextPrimary + } else { + colorTextPrimary + }, + textAlign = TextAlign.Center, + maxLines = 1 + ) +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/RedirectionCtaWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/RedirectionCtaWidgetComposable.kt new file mode 100644 index 0000000000..fd6d64aefa --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/RedirectionCtaWidgetComposable.kt @@ -0,0 +1,25 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.model.RedirectionCtaWidgetData + +@Composable +fun RedirectionCtaWidgetComposable( + redirectionCtaWidgetComposable: RedirectionCtaWidgetData?, + widgetCallback: WidgetCallback? +) { + LaunchedEffect(key1 = redirectionCtaWidgetComposable) { + redirectionCtaWidgetComposable?.widgetData?.cta?.let { ctaData -> + widgetCallback?.onClick(ctaData) + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/SelectableTextWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/SelectableTextWidgetComposable.kt new file mode 100644 index 0000000000..50cbab8f9d --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/SelectableTextWidgetComposable.kt @@ -0,0 +1,62 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.model.SelectableTextWidgetData +import com.navi.naviwidgets.composewidget.reusable.CustomCheckBox +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.naviwidgets.extensions.setWidgetLayoutParams +import com.navi.naviwidgets.models.GenericWidgetDataInfo + +@Composable +fun SelectableTextWidgetComposable( + selectableTextData: SelectableTextWidgetData?, + widgetCallback: WidgetCallback? = null, + onWidgetUpdate: (updatedData: GenericWidgetDataInfo, isValid: Boolean) -> Unit +) { + val isChecked = remember { + mutableStateOf(selectableTextData?.widgetData?.checkboxData?.isChecked ?: false) + } + selectableTextData?.widgetData?.let { data -> + setWidgetLayoutParams(widgetLayoutParams = selectableTextData?.widgetLayoutParams) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.Top, + horizontalArrangement = Arrangement.Start + ) { + CustomCheckBox( + modifier = Modifier.padding(end = 8.dp), + checked = isChecked.value, + onCheckedChange = { checked -> + isChecked.value = checked + selectableTextData + ?.copy( + widgetData = + selectableTextData?.widgetData?.copy(value = isChecked.value) + ) + ?.let { onWidgetUpdate.invoke(it, isChecked.value) } + }, + selectedColor = hexToColor(data.checkboxData?.backgroundColor) + ) + NaviTextWidgetized(textFieldData = data.title, widgetCallback = widgetCallback) + } + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/SingleImageWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/SingleImageWidgetComposable.kt new file mode 100644 index 0000000000..b96ba973cf --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/SingleImageWidgetComposable.kt @@ -0,0 +1,56 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.setWidgetLayoutParams +import com.navi.naviwidgets.models.response.ImageFieldData +import com.navi.naviwidgets.models.response.SingleImageWidget + +@Composable +fun SingleImageWidgetComposable( + singleImageWidget: SingleImageWidget?, + widgetCallback: WidgetCallback? = null +) { + val widgetData = singleImageWidget?.widgetData() + setWidgetLayoutParams(widgetLayoutParams = singleImageWidget?.widgetLayoutParams) { + val marginParams = singleImageWidget?.widgetLayoutParams?.margin?.trim()?.split(",") + val horizontalMargin = + try { + ((marginParams?.getOrNull(0)?.trim()?.toInt() ?: 0) + + (marginParams?.getOrNull(2)?.trim()?.toInt() ?: 0)) + } catch (e: Exception) { + 0 + } + val screenWidth = LocalConfiguration.current.screenWidthDp + val width = (screenWidth - horizontalMargin).dp + val height = + ((screenWidth - horizontalMargin) / (widgetData?.aspectRatio ?: 1).toDouble()).dp + NaviImage( + modifier = + Modifier.width(width) + .height(height) + .background( + color = Color.White, + shape = RoundedCornerShape((widgetData?.cornerRadius ?: 0).dp) + ), + imageFieldData = ImageFieldData(url = widgetData?.imageUrl), + widgetCallback + ) + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/TitleWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/TitleWidgetComposable.kt index 45a806ce8c..1256377b6a 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/TitleWidgetComposable.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/TitleWidgetComposable.kt @@ -42,6 +42,7 @@ fun TitleWidgetComposable(titleWidget: TitleWidget?, widgetCallback: WidgetCallb widgetData?.widgetData()?.let { data -> data.title?.let { title -> NaviTextWidgetized( + modifier = Modifier.fillMaxWidth(), textFieldData = title, widgetCallback = widgetCallback, debounceDelay = 600 @@ -49,7 +50,7 @@ fun TitleWidgetComposable(titleWidget: TitleWidget?, widgetCallback: WidgetCallb } data.subTitle?.let { title -> NaviTextWidgetized( - modifier = Modifier.padding(top = 8.dp), + modifier = Modifier.padding(top = 8.dp).fillMaxWidth(), textFieldData = title, widgetCallback = widgetCallback, debounceDelay = 600 diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/ToastListWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/ToastListWidgetComposable.kt index 8d2adc968d..87f766c42b 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/ToastListWidgetComposable.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/ToastListWidgetComposable.kt @@ -27,7 +27,7 @@ import com.navi.naviwidgets.models.response.ToastListWidgetData fun ToastListWidgetComposable( widgetData: ToastListWidgetData?, widgetCallback: WidgetCallback? = null, - onWidgetUpdate: (updatedData: ToastListWidgetData) -> Unit + onWidgetUpdate: (updatedData: ToastListWidgetData, isValid: Boolean) -> Unit ) { var dataList by remember( @@ -64,7 +64,9 @@ fun ToastListWidgetComposable( ?.copy(toastList = updatedList) val updatedData = widgetData?.copy(widgetData = updatedToastData) - updatedData?.let { onWidgetUpdate(updatedData) } + updatedData?.let { + onWidgetUpdate(updatedData, true) + } } } } diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/ComposeWidgetExt.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/ComposeWidgetExt.kt index 62ddff7dc4..fc2c0cb336 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/ComposeWidgetExt.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/ComposeWidgetExt.kt @@ -7,8 +7,10 @@ package com.navi.naviwidgets.extensions +import android.content.Context import android.graphics.Color.parseColor import android.graphics.drawable.GradientDrawable +import android.view.LayoutInflater import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable @@ -26,6 +28,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -70,6 +73,7 @@ import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow @@ -77,6 +81,7 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView import coil.compose.AsyncImage import coil.request.CachePolicy import coil.request.ImageRequest @@ -84,6 +89,7 @@ import com.airbnb.lottie.compose.LottieAnimation import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.animateLottieCompositionAsState import com.airbnb.lottie.compose.rememberLottieComposition +import com.facebook.shimmer.ShimmerFrameLayout import com.google.gson.Gson import com.navi.alfred.utils.log import com.navi.base.model.Padding @@ -96,6 +102,7 @@ import com.navi.design.theme.getFontWeight import com.navi.design.theme.ttComposeFontFamily import com.navi.design.utils.BackgroundDrawableData import com.navi.design.utils.Constants +import com.navi.design.utils.KeyboardInputType import com.navi.design.utils.getFontStyle import com.navi.design.utils.isValidHexColor import com.navi.design.utils.parseColorSafe @@ -953,7 +960,7 @@ fun hexToColor(hex: String?): Color { hex?.let { return Color(it.parseColorSafe()) } - return Color.Unspecified + return Color.White } fun getSubstringIndexes(substring: String, parentString: String): Pair { @@ -1270,3 +1277,21 @@ fun NaviGrid( Spacer(modifier = Modifier.height(spacerHeight.orZero().dp)) } } + +fun getKeyBoardType(keyboardType: String?): KeyboardType { + return when (keyboardType) { + KeyboardInputType.NUMERIC.name -> KeyboardType.Number + KeyboardInputType.PHONE.name -> KeyboardType.Phone + KeyboardInputType.EMAIL.name -> KeyboardType.Email + else -> KeyboardType.Unspecified + } +} + +@Composable +fun ShowShimmer(loading: Boolean, context: Context, layoutId: Int) { + if (loading) { + val layout = ShimmerFrameLayout(context) + layout.addView(LayoutInflater.from(context).inflate(layoutId, layout, false)) + AndroidView(modifier = Modifier.fillMaxSize().background(Color.White), factory = { layout }) + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/WidgetExt.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/WidgetExt.kt index f9c38ee1d0..f5ab78a583 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/WidgetExt.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/WidgetExt.kt @@ -1441,6 +1441,8 @@ fun ImageView.setImageFieldData(imageFieldData: ImageFieldData?) { imageFieldData?.let { data -> visibility = VISIBLE val imageResId = getImageFromIconCode(imageFieldData.iconCode) + imageFieldData.iconWidth?.let { layoutParams.width = dpToPxInInt(it) } + imageFieldData.iconHeight?.let { layoutParams.height = dpToPxInInt(it) } if (imageResId == -1) { imageFieldData.url?.let { url -> loadUrlIntoImageView(this.context, url, this) } } else { diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/utils/Utils.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/utils/Utils.kt index a5e74172b5..bade27a30c 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/utils/Utils.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/utils/Utils.kt @@ -34,6 +34,15 @@ import android.widget.TextView import android.widget.Toast import androidx.appcompat.widget.AppCompatTextView import androidx.cardview.widget.CardView +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible @@ -113,6 +122,7 @@ import java.util.Calendar import java.util.Date import java.util.Locale import java.util.concurrent.TimeUnit +import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -1223,3 +1233,21 @@ fun EditText.openKeyboard(context: Context, delay: Long = 100) { e.log() } } + +data class DottedShape( + val step: Dp, +) : Shape { + override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density) = + Outline.Generic( + Path().apply { + val stepPx = with(density) { step.toPx() } + val stepsCount = (size.width / stepPx).roundToInt() + val actualStep = size.width / stepsCount + val dotSize = Size(width = actualStep / 2, height = size.height) + for (i in 0 until stepsCount) { + addRect(Rect(offset = Offset(x = i * actualStep, y = 0f), size = dotSize)) + } + close() + } + ) +} diff --git a/components/reusable/tooltip/TooltipStyle.ts b/components/reusable/tooltip/TooltipStyle.ts index c25d5bccd9..538a978a24 100644 --- a/components/reusable/tooltip/TooltipStyle.ts +++ b/components/reusable/tooltip/TooltipStyle.ts @@ -1,25 +1,25 @@ import { StyleSheet } from "react-native"; const styles = StyleSheet.create({ - container: { - flexDirection: "column", - alignItems: "center", - }, - box: { - position: "relative", - }, - tooltipBorder: { - borderLeftColor: "transparent", - borderRightColor: "transparent", - borderStyle: "solid", - }, - tooltipInner: { - position: "absolute", - borderLeftColor: "transparent", - borderRightColor: "transparent", - borderStyle: "solid", - zIndex: 2, - }, + container: { + flexDirection: "column", + alignItems: "center", + }, + box: { + position: "relative", + }, + tooltipBorder: { + borderLeftColor: "transparent", + borderRightColor: "transparent", + borderStyle: "solid", + }, + tooltipInner: { + position: "absolute", + borderLeftColor: "transparent", + borderRightColor: "transparent", + borderStyle: "solid", + zIndex: 2, + }, }); - -export default styles; \ No newline at end of file + +export default styles;