diff --git a/App/common/interface/widgets/widgetData/CardWithIconWidgetData.ts b/App/common/interface/widgets/widgetData/CardWithIconWidgetData.ts index 6eab0971fd..c7fbe31581 100644 --- a/App/common/interface/widgets/widgetData/CardWithIconWidgetData.ts +++ b/App/common/interface/widgets/widgetData/CardWithIconWidgetData.ts @@ -1,12 +1,15 @@ +import { ViewStyle } from "react-native"; import { CtaData } from "../.."; import { GenericWidgetData } from "../Widget"; import { ButtonData } from "./FooterWithCardWidgetData"; -import { ImageFieldData, TextFieldData, TitleWidgetData } from "./TitleWidgetData"; +import { ImageFieldData, TextFieldData } from "./TitleWidgetData"; export interface CardWithIconWidgetData extends GenericWidgetData { title?: TextFieldData; subtitle?: TextFieldData; - button?: ButtonData + button?: ButtonData; rightIcon?: ImageFieldData; - cta?: CtaData + cta?: CtaData; + containerStyle?: ViewStyle; + columnContainerStyle?: ViewStyle; } 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 f3a438a0ad..326bdcebd8 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 @@ -74,6 +74,7 @@ class NaviInsuranceAnalytics private constructor() { const val QUOTE_SUM_INSURED = "quote_sum_insured" const val QUOTE_PREMIUM_AMOUNT = "quote_premium_amount" const val POLICY_DETAILS_SCREEN = "policy_details_screen" + const val HOME_VISIT_LANDING_SCREEN = "home_visit_landing_screen" const val GI_EVENT_INIT_SCREEN_HEALTH_ISSUES = "gi_event_init_screen_health_issues_screen" const val GI_EVENT_INIT_SCREEN_PINCODE_INPUT = "gi_event_init_screen_pincode_input_screen" const val GI_EVENT_INIT_SCREEN_NSTP_MEMBER_SELECTION = diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/common/models/GiErrorMetaData.kt b/android/navi-insurance/src/main/java/com/navi/insurance/common/models/GiErrorMetaData.kt index dcf6836829..d8a0f0be2c 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/common/models/GiErrorMetaData.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/common/models/GiErrorMetaData.kt @@ -59,5 +59,6 @@ data class GiErrorMetaData( const val TRIAL_INSURANCE_FLOW = "trial_insurance" const val FLOW_STATIC_DIGITAL_CLAIM = "static_digital_claim" const val FLOW_UITRON_BOTTOM_SHEET = "uitron_bottom_sheet" + const val FLOW_HOME_VISIT = "home_visit" } } 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 f430b9b8d5..0255e0569d 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 @@ -424,5 +424,6 @@ class NavigationHandler @Inject constructor(private val uiControllerUtil: UiCont const val POLICY_SELECTOR_BOTTOMSHEET = "policy_selector_bottomsheet" const val AHC_ODC_TABULAR_BOTTOMSHEET = "ahc_odc_tabular_bottomsheet" const val AUTOPAY_NUDGE_BOTTOMSHEET = "autopay_nudge_bottomsheet" + const val HOME_VISIT_SCREEN = "gi_home_visit" } } 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 621e7e2332..ca4e879986 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 @@ -112,6 +112,7 @@ import com.navi.insurance.health.fragment.PaymentAttentionBottomSheet import com.navi.insurance.health.fragment.PaymentMethodSelectionFragment import com.navi.insurance.health.fragment.QuoteBenefitsFragmentV2 import com.navi.insurance.health.fragment.UpdatedPremiumBottomSheet +import com.navi.insurance.home_visit.HomeVisitLandingPageFragment import com.navi.insurance.kyc.ui.GiAadhaarVerificationFragment import com.navi.insurance.member_name_dob_details.ui.MemberNameDobFragment import com.navi.insurance.new_hospital_experience.fragment.GiNewHospitalsFragment @@ -208,6 +209,7 @@ class UiControllerUtil @Inject constructor() { NavigationHandler.HI_ABHA_SCREEN -> ABHAFragment() NavigationHandler.HI_AADHAR_SCREEN -> ABHAaadharFragment() NavigationHandler.HI_OTP_SCREEN -> ABHAOtpFragment() + NavigationHandler.HOME_VISIT_SCREEN -> HomeVisitLandingPageFragment() else -> null } } @@ -306,6 +308,7 @@ class UiControllerUtil @Inject constructor() { NavigationHandler.HI_AADHAR_SCREEN -> ABHAaadharFragment.TAG NavigationHandler.HI_OTP_SCREEN -> ABHAOtpFragment.TAG NavigationHandler.POLICY_DETAILS_SCREEN -> PolicyDetailsFragment.TAG + NavigationHandler.HOME_VISIT_SCREEN -> HomeVisitLandingPageFragment.TAG else -> "" } } diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitLandingPageFragment.kt b/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitLandingPageFragment.kt new file mode 100644 index 0000000000..7278e81d1c --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitLandingPageFragment.kt @@ -0,0 +1,330 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.home_visit + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +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.foundation.lazy.rememberLazyListState +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.lifecycleScope +import com.facebook.shimmer.ShimmerFrameLayout +import com.google.gson.reflect.TypeToken +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.NaviClickAction +import com.navi.base.utils.isNotNull +import com.navi.base.utils.orFalse +import com.navi.base.utils.orZero +import com.navi.common.ResponseState +import com.navi.common.ui.fragment.BaseFragment +import com.navi.insurance.R +import com.navi.insurance.analytics.InsuranceAnalyticsHandler +import com.navi.insurance.analytics.NaviInsuranceAnalytics +import com.navi.insurance.navigator.NaviInsuranceDeeplinkNavigator +import com.navi.insurance.policy.details.HomeVisitResponse +import com.navi.insurance.util.Constants +import com.navi.insurance.util.isTrue +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.composewidget.GenericComposableWidgetFactory +import com.navi.naviwidgets.composewidget.reusable.footerColorShadow +import com.navi.naviwidgets.extensions.getJsonObject +import com.navi.naviwidgets.extensions.isFirstItemVisible +import com.navi.naviwidgets.models.GenericWidgetDataInfo +import com.navi.naviwidgets.models.response.NaviErrorPageWidget +import com.navi.naviwidgets.models.response.TextFieldData +import com.navi.naviwidgets.utils.NaviWidgetIconUtils +import com.navi.naviwidgets.views.NaviErrorPageView +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +@AndroidEntryPoint +class HomeVisitLandingPageFragment : BaseFragment(), WidgetCallback { + private val viewModel by viewModels() + private var view: NaviErrorPageView? = null + + @Inject lateinit var analyticsHandler: InsuranceAnalyticsHandler + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + view = NaviErrorPageView(requireContext()) + val composeView = ComposeView(requireContext()).apply { setContent { HomeVisitScreen() } } + sendLandingAnalyticEvent() + return composeView + } + + private fun sendLandingAnalyticEvent() { + val analyticsEvent = AnalyticsEvent(name = NaviInsuranceAnalytics.HOME_VISIT_LANDING_SCREEN) + analyticsHandler.sendEvent(analyticsEvent, screenName) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.fetchHomeVisitLandingPageResponse() + observeCallbackData() + } + + @Composable + fun HomeVisitScreen() { + val state = viewModel.homeVisitResponseFlow.collectAsState() + Box(modifier = Modifier.fillMaxSize()) { + when { + state.value.isLoading.isTrue() -> { + ShowShimmer(state.value.isLoading) + } + state.value.hasErrorOccured.isTrue() -> { + InitErrorView(state.value.hasErrorOccured) + } + state.value.data.isNotNull() -> { + state.value.data?.let { pageResponse -> HomeVisitLandingPage(pageResponse) } + } + } + } + } + + @OptIn(ExperimentalFoundationApi::class) + @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") + @Composable + fun HomeVisitLandingPage(data: HomeVisitResponse) { + val widgetCallback: WidgetCallback = this + val listState = rememberLazyListState() + val isFirstItemVisible = listState.isFirstItemVisible() + val buttonState = viewModel.footerButtonStateFlow.collectAsStateWithLifecycle() + Box { + Scaffold( + modifier = + Modifier.fillMaxSize() + .animateContentSize( + animationSpec = tween(durationMillis = 600, delayMillis = 0) + ), + topBar = { + data.header?.getOrNull(0)?.let { data -> + Box(modifier = Modifier.background(Color.White)) { + GenericComposableWidgetFactory( + data = data, + this@HomeVisitLandingPageFragment, + isFirstItemVisible + ) + } + } + }, + bottomBar = { + data.footer?.getOrNull(0)?.let { data -> + AnimatedVisibility( + modifier = Modifier, + visible = !isFirstItemVisible, + enter = slideInVertically { it }, + exit = slideOutVertically { it } + ) { + FooterComposable( + data = data, + widgetCallback = widgetCallback, + isFirstItemVisible = isFirstItemVisible + ) + } + } + } + ) { + CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + Box(modifier = Modifier.background(Color.White).padding(top = 56.dp)) { + LazyColumn( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + state = listState + ) { + items(data.content?.size.orZero(), key = { it.toString() }) { index -> + val genericWidgetData = data.content?.getOrNull(index) + Column(modifier = Modifier.fillMaxWidth().wrapContentSize()) { + GenericComposableWidgetFactory( + state = buttonState.value, + data = genericWidgetData, + widgetCallback = widgetCallback + ) + } + } + } + } + } + } + } + } + + @Composable + fun FooterComposable( + data: GenericWidgetDataInfo, + widgetCallback: WidgetCallback?, + isFirstItemVisible: Boolean = true + ) { + val footerState = viewModel.footerButtonStateFlow.collectAsStateWithLifecycle() + Column( + modifier = + Modifier.background( + brush = + Brush.verticalGradient( + colors = listOf(Color.Transparent, footerColorShadow) + ), + shape = RectangleShape + ) + .padding(top = 35.dp) + ) { + Box(modifier = Modifier.background(Color.White)) { + GenericComposableWidgetFactory( + state = footerState.value, + data = data, + widgetCallback = widgetCallback, + isFirstItemVisible = isFirstItemVisible + ) + } + } + } + + @Composable + fun ShowShimmer(isLoading: Boolean) { + if (isLoading) { + val layout = ShimmerFrameLayout(requireContext()) + layout.addView( + LayoutInflater.from(requireContext()) + .inflate(R.layout.navi_insurance_shimmer_tab_layout, layout, false) + ) + AndroidView( + modifier = Modifier.fillMaxSize().background(Color.White), + factory = { layout } + ) + } + } + + @Composable + fun InitErrorView(hasErrorOccured: Boolean) { + if (hasErrorOccured) { + view?.setProperties( + naviErrorPageWidget = + NaviErrorPageWidget( + title = TextFieldData(text = Constants.SOMETHING_WENT_WRONG), + subTitle = TextFieldData(text = Constants.ERROR_SUBTITLE), + buttonText = TextFieldData(text = Constants.RETRY), + iconName = NaviWidgetIconUtils.SOMETHING_WENT_WRONG + ), + callback = + object : NaviErrorPageView.Callback { + override fun onRetryClick(tag: String?) { + viewModel.fetchHomeVisitLandingPageResponse() + } + + override fun onCloseButtonClick() { + onClick(CtaData(type = CtaType.GO_BACK.value)) + } + + override val showCloseButton: Boolean + get() = true + } + ) + view?.let { HandleError(it) } + } + } + + @Composable + fun HandleError(view: NaviErrorPageView) { + AndroidView(modifier = Modifier.fillMaxSize().background(Color.White), factory = { view }) + } + + 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.CALLBACK_STATUS.name -> { + handleErrorCta(cta = naviClickAction) + viewModel.getHomeVisitStatusCta() + } + else -> { + NaviInsuranceDeeplinkNavigator.navigate( + activity, + ctaData = naviClickAction, + finish = naviClickAction.finish.orFalse(), + clearTask = naviClickAction.clearTask.orFalse() + ) + } + } + } + } + + private fun observeCallbackData() { + viewModel.callbackStatusFlow + .onEach { + when (it) { + is ResponseState.Failure -> { + viewModel.homeVisitStatusErrorCta?.let { it1 -> onClick(it1, null) } + } + ResponseState.Idle -> Unit + ResponseState.Loading -> Unit + is ResponseState.Success -> { + onClick(it.value, null) + } + } + } + .launchIn(viewLifecycleOwner.lifecycleScope) + } + + private fun handleErrorCta(cta: CtaData) { + cta.parameters?.forEach { keyValue -> + if (keyValue.key == Constants.ERROR_CTA) { + val dataType = object : TypeToken() {}.type + val errorCta = getJsonObject(dataType, keyValue.value) + viewModel.homeVisitStatusErrorCta = errorCta + } + } + } + + override val screenName: String = TAG + + companion object { + val TAG = "HomeVisitLandingPageFragment" + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitRepo.kt b/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitRepo.kt new file mode 100644 index 0000000000..c08609530c --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitRepo.kt @@ -0,0 +1,33 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.home_visit + +import com.navi.base.model.CtaData +import com.navi.common.network.models.RepoResult +import com.navi.common.network.retrofit.ResponseCallback +import com.navi.insurance.common.models.CallbackRequestBody +import com.navi.insurance.network.retrofit.RetrofitService +import com.navi.insurance.policy.details.HomeVisitResponse +import javax.inject.Inject + +class HomeVisitRepo @Inject constructor(private val retrofitService: RetrofitService) : + ResponseCallback() { + + suspend fun fetchHomeVisitScreen(): RepoResult = + apiResponseCallback(retrofitService.fetchVisitHomePageResponse()) + + suspend fun getHomeVisitStatusCta( + screenName: String, + ): RepoResult = + apiResponseCallback( + retrofitService.getCallbackStatusCTA( + screenName = screenName, + callbackRequestBody = CallbackRequestBody() + ) + ) +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitResponse.kt b/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitResponse.kt new file mode 100644 index 0000000000..7d618d3f0e --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitResponse.kt @@ -0,0 +1,24 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.policy.details + +import com.google.gson.annotations.SerializedName +import com.navi.naviwidgets.models.GenericWidgetDataInfo + +data class HomeVisitResponse( + @SerializedName("footer") val footer: List? = null, + @SerializedName("header") val header: List? = null, + @SerializedName("content") val content: List? = null, + @SerializedName("pageMetaData") val pageMetaData: PageMetaData? = null +) + +data class HomeVisitScreenState( + val isLoading: Boolean = false, + val hasErrorOccured: Boolean = false, + val data: HomeVisitResponse? = null +) diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitVM.kt b/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitVM.kt new file mode 100644 index 0000000000..c750b80cbf --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/home_visit/HomeVisitVM.kt @@ -0,0 +1,121 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.home_visit + +import androidx.lifecycle.viewModelScope +import com.navi.base.model.CtaData +import com.navi.base.model.NaviClickAction +import com.navi.base.utils.isNotNull +import com.navi.base.utils.isNull +import com.navi.common.ResponseState +import com.navi.common.di.CoroutineDispatcherProvider +import com.navi.common.network.models.ErrorMetaData +import com.navi.common.viewmodel.BaseVM +import com.navi.insurance.common.models.GiErrorMetaData +import com.navi.insurance.network.ApiErrorTagType +import com.navi.insurance.policy.details.HomeVisitScreenState +import com.navi.insurance.util.Constants +import com.navi.naviwidgets.models.FooterButtonState +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +@HiltViewModel +class HomeVisitVM +@Inject +constructor( + private val repository: HomeVisitRepo, + private val dispatcher: CoroutineDispatcherProvider +) : BaseVM() { + var homeVisitStatusErrorCta: NaviClickAction? = null + + private val _homeVisitResponseFlow = MutableStateFlow(HomeVisitScreenState()) + val homeVisitResponseFlow: StateFlow + get() = _homeVisitResponseFlow.asStateFlow() + + private val _callbackStatusFlow = MutableSharedFlow>() + val callbackStatusFlow: SharedFlow> + get() = _callbackStatusFlow.asSharedFlow() + + private val _footerButtonStateFlow = MutableStateFlow(FooterButtonState.ENABLED.name) + val footerButtonStateFlow: StateFlow + get() = _footerButtonStateFlow.asStateFlow() + + fun fetchHomeVisitLandingPageResponse() { + viewModelScope.launch(dispatcher.io) { + _homeVisitResponseFlow.update { + it.copy(isLoading = true, data = null, hasErrorOccured = false) + } + val response = repository.fetchHomeVisitScreen() + if ( + response.error.isNull() && + response.errors.isNullOrEmpty() && + response.data.isNotNull() + ) { + response.data?.apply { + _homeVisitResponseFlow.update { + it.copy(isLoading = false, data = this, hasErrorOccured = false) + } + } + } else { + _homeVisitResponseFlow.update { + it.copy(isLoading = false, data = null, hasErrorOccured = true) + } + setErrorData( + response.errors, + response.error, + showFullScreenError = true, + errorMetaData = + ErrorMetaData( + methodName = ApiErrorTagType.HOME_VISIT_SCREEN_LOAD_ERROR.value, + flowName = GiErrorMetaData.FLOW_HOME_VISIT + ) + ) + } + } + } + + fun getHomeVisitStatusCta() { + viewModelScope.launch(dispatcher.io) { + _footerButtonStateFlow.value = FooterButtonState.LOADING.name + val response = + repository.getHomeVisitStatusCta( + screenName = Constants.HOME_VISIT_SCREEN, + ) + _footerButtonStateFlow.value = FooterButtonState.ENABLED.name + if ( + response.error.isNull() && + response.errors.isNullOrEmpty() && + response.data.isNotNull() + ) { + response.data?.apply { _callbackStatusFlow.emit(ResponseState.Success(this)) } + } else { + _callbackStatusFlow.emit( + ResponseState.Failure(ApiErrorTagType.HOME_VISIT_STATUS_CTA_ERROR.value) + ) + setErrorData( + response.errors, + response.error, + showFullScreenError = false, + errorMetaData = + ErrorMetaData( + methodName = ApiErrorTagType.HOME_VISIT_STATUS_CTA_ERROR.value, + flowName = GiErrorMetaData.FLOW_HOME_VISIT + ) + ) + } + } + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/mock_api_util/MockApiUtil.kt b/android/navi-insurance/src/main/java/com/navi/insurance/mock_api_util/MockApiUtil.kt index 134bb8686d..c08d4de6cd 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/mock_api_util/MockApiUtil.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/mock_api_util/MockApiUtil.kt @@ -49,7 +49,7 @@ fun mockApiResponse(type: Type, jsonKey: String): RepoResult { .registerUiTronDeSerializers() .registerTypeAdapter(RightSideContentData::class.java, MultiSelectDataDeserializer()) .create() - val inputStream = AppServiceManager.application.resources.openRawResource(R.raw.mock_api) + val inputStream = AppServiceManager.application.resources.openRawResource(R.raw.mock_api_gi) val dataString = String(inputStream.readBytes(), StandardCharsets.UTF_8) val jsonElement = (JsonParser.parseString(dataString) as? JsonObject)?.get(jsonKey) return RepoResult(statusCode = 200, data = customDeserializer.fromJson(jsonElement, type)) diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/network/ApiErrorTagType.kt b/android/navi-insurance/src/main/java/com/navi/insurance/network/ApiErrorTagType.kt index ec8db321e8..1de7dbabd7 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/network/ApiErrorTagType.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/network/ApiErrorTagType.kt @@ -162,5 +162,7 @@ enum class ApiErrorTagType(val value: String) { SUBMIT_EIA_INFO_ERROR("SUBMIT_EIA_INFO_ERROR"), FETCH_EIA_INFO_ERROR("FETCH_EIA_INFO_ERROR"), FETCH_HOSPITAL_RESULT_PAGE_ERROR("FETCH_HOSPITAL_RESULT_PAGE_ERROR"), - POLICY_DETAILS_LOAD_ERROR("POLICY_DETAILS_LOAD_ERROR") + POLICY_DETAILS_LOAD_ERROR("POLICY_DETAILS_LOAD_ERROR"), + HOME_VISIT_SCREEN_LOAD_ERROR("HOME_VISIT_SCREEN_LOAD_ERROR"), + HOME_VISIT_STATUS_CTA_ERROR("HOME_VISIT_STATUS_CTA_ERROR"), } 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 f25985e794..02207cbcb3 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 @@ -155,6 +155,7 @@ import com.navi.insurance.models.response.UpdateProposalResponse import com.navi.insurance.models.response.UserInputRequestData import com.navi.insurance.new_hospital_experience.model.response.HospitalNewSearchResponse import com.navi.insurance.new_hospital_experience.model.response.HospitalPageDataResponse +import com.navi.insurance.policy.details.HomeVisitResponse import com.navi.insurance.policy.details.PolicyDetailsResponse import com.navi.insurance.pre.purchase.journey.FormWidgetRequest import com.navi.insurance.pre.purchase.journey.PreQuoteJourneyPageResponse @@ -320,6 +321,9 @@ interface RetrofitService { @Body requestBody: RequestCallbackReq ): Response>> + @GET("/customer-callback/screen/home-visit") + suspend fun fetchVisitHomePageResponse(): Response> + @GET("/benefits/v1") suspend fun fetchBenefits( @Query("id") id: String, diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/util/Constants.kt b/android/navi-insurance/src/main/java/com/navi/insurance/util/Constants.kt index 02d3060b54..8259ce6cda 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/util/Constants.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/util/Constants.kt @@ -275,6 +275,11 @@ object Constants { const val POLL = "POLL" const val ANIMATE_FOOTER_SECTION_VISIBILITY = "animateFooterSectionVisibility" const val STATUS_BAR_COLOR = "statusBarColor" + const val SOMETHING_WENT_WRONG = "Something went wrong" + const val ERROR_SUBTITLE = "It appears we are facing trouble.\\nPlease try again" + const val RETRY = "Retry" + const val ERROR_CTA = "errorCta" + const val HOME_VISIT_SCREEN = "home_visit_screen" // This is required for animating a component for Health risk score val numberConstant: List = diff --git a/android/navi-insurance/src/main/res/raw/mock_api.json b/android/navi-insurance/src/main/res/raw/mock_api_gi.json similarity index 100% rename from android/navi-insurance/src/main/res/raw/mock_api.json rename to android/navi-insurance/src/main/res/raw/mock_api_gi.json 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 ba8aa58b49..aebae1ccd7 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 @@ -471,6 +471,21 @@ class WidgetDataDeserializer : JsonDeserializer { WidgetTypes.TITLE_WITH_BUTTON_AND_ICON_WIDGET.value -> { Gson().fromJson(jsonObject, TitleWithButtonAndIconWidgetData::class.java) } + WidgetTypes.CARD_WITH_TITLE_FOOTER_IMAGE.value -> { + Gson().fromJson(jsonObject, CardWithTitleFooterImageWidgetData::class.java) + } + WidgetTypes.STEPS_ITEM_LIST_WIDGET.value -> { + Gson().fromJson(jsonObject, StepsItemListWidget::class.java) + } + WidgetTypes.CARD_WITH_TITLE_CONTENT_LIST_WIDGET.value -> { + Gson().fromJson(jsonObject, CardWithTitleContentListWidget::class.java) + } + WidgetTypes.LIST_WITH_DROPDOWN_WIDGET.value -> { + Gson().fromJson(jsonObject, ListWithDropdownWidget::class.java) + } + WidgetTypes.CARD_WITH_LOTTIE_TEXT_BUTTON.value -> { + Gson().fromJson(jsonObject, CardWithLottieTextButtonWidget::class.java) + } else -> { Gson().fromJson(jsonObject, GenericWidgetDataInfo::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 e64b28bb6a..e97ff038cc 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 @@ -136,5 +136,10 @@ 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"), - TITLE_WITH_BUTTON_AND_ICON_WIDGET("TITLE_WITH_BUTTON_AND_ICON_WIDGET") + TITLE_WITH_BUTTON_AND_ICON_WIDGET("TITLE_WITH_BUTTON_AND_ICON_WIDGET"), + CARD_WITH_TITLE_FOOTER_IMAGE("CARD_WITH_TITLE_FOOTER_IMAGE"), + CARD_WITH_LOTTIE_TEXT_BUTTON("CARD_WITH_LOTTIE_TEXT_BUTTON"), + CARD_WITH_TITLE_CONTENT_LIST_WIDGET("CARD_WITH_TITLE_CONTENT_LIST_WIDGET"), + LIST_WITH_DROPDOWN_WIDGET("LIST_WITH_DROPDOWN_WIDGET"), + STEPS_ITEM_LIST_WIDGET("STEPS_ITEM_LIST_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 54d6433116..167ca13b9c 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 @@ -25,6 +25,9 @@ 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.CardWithLottieTextButtonWidgetComposable +import com.navi.naviwidgets.composewidget.widgets.CardWithTitleContentListWidgetComposable +import com.navi.naviwidgets.composewidget.widgets.CardWithTitleFooterImageWidgetComposable import com.navi.naviwidgets.composewidget.widgets.CenteredIconTextWidgetComposable import com.navi.naviwidgets.composewidget.widgets.DismissableToastWidgetData import com.navi.naviwidgets.composewidget.widgets.EditableTextWidgetComposable @@ -37,6 +40,7 @@ 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.ListWithDropdownWidgetComposable import com.navi.naviwidgets.composewidget.widgets.OTPWidgetComposable import com.navi.naviwidgets.composewidget.widgets.PolicyCardCarouselWidgetComposable import com.navi.naviwidgets.composewidget.widgets.PolicyCardWidgetComposable @@ -46,6 +50,7 @@ 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.StepsItemListWidgetComposable import com.navi.naviwidgets.composewidget.widgets.TableWithTooltipAndCtaWidgetComposable import com.navi.naviwidgets.composewidget.widgets.TitleCardCtaWidgetComposable import com.navi.naviwidgets.composewidget.widgets.TitleContentFooterCardWidgetComposable @@ -67,6 +72,9 @@ import com.navi.naviwidgets.models.SeparatorWidgetComposableData import com.navi.naviwidgets.models.TitleContentFooterCardWidgetData import com.navi.naviwidgets.models.TitleContentImageFooterWithCtaWidgetData import com.navi.naviwidgets.models.response.CardWithListItemsWidgetData +import com.navi.naviwidgets.models.response.CardWithLottieTextButtonWidget +import com.navi.naviwidgets.models.response.CardWithTitleContentListWidget +import com.navi.naviwidgets.models.response.CardWithTitleFooterImageWidgetData import com.navi.naviwidgets.models.response.DismissableToastWidgetData import com.navi.naviwidgets.models.response.FooterSectionWidgetData import com.navi.naviwidgets.models.response.GridWithAssetCardWidgetData @@ -74,10 +82,12 @@ import com.navi.naviwidgets.models.response.GridWithCtaWidgetData import com.navi.naviwidgets.models.response.HorizontalCardCarouselWidgetData import com.navi.naviwidgets.models.response.ImageTitleCardWidgetData import com.navi.naviwidgets.models.response.LeftRightTitleListWidgetData +import com.navi.naviwidgets.models.response.ListWithDropdownWidget 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.StepsItemListWidget import com.navi.naviwidgets.models.response.TableWithTooltipAndCtaWidgetData import com.navi.naviwidgets.models.response.TitleCardCtaWidgetData import com.navi.naviwidgets.models.response.TitleWidget @@ -239,6 +249,13 @@ fun GenericComposableWidgetFactory( widgetCallback ) } + WidgetTypes.CARD_WITH_TITLE_FOOTER_IMAGE.value -> { + CardWithTitleFooterImageWidgetComposable( + (data as? CardWithTitleFooterImageWidgetData), + state = state, + widgetCallback + ) + } WidgetTypes.FOOTER_SECTION_TEMPLATE.value -> { FooterSectionWidgetComposable((data as? FooterSectionWidgetData), widgetCallback) } @@ -285,5 +302,23 @@ fun GenericComposableWidgetFactory( widgetCallback ) } + WidgetTypes.CARD_WITH_LOTTIE_TEXT_BUTTON.value -> { + CardWithLottieTextButtonWidgetComposable( + (data as? CardWithLottieTextButtonWidget), + widgetCallback + ) + } + WidgetTypes.STEPS_ITEM_LIST_WIDGET.value -> { + StepsItemListWidgetComposable((data as? StepsItemListWidget), widgetCallback) + } + WidgetTypes.CARD_WITH_TITLE_CONTENT_LIST_WIDGET.value -> { + CardWithTitleContentListWidgetComposable( + (data as? CardWithTitleContentListWidget), + widgetCallback + ) + } + WidgetTypes.LIST_WITH_DROPDOWN_WIDGET.value -> { + ListWithDropdownWidgetComposable((data as? ListWithDropdownWidget), widgetCallback) + } } } diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CardWithLottieTextWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CardWithLottieTextWidgetComposable.kt new file mode 100644 index 0000000000..62640c9ac5 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CardWithLottieTextWidgetComposable.kt @@ -0,0 +1,148 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +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.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviLottie +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.debounceClick +import com.navi.naviwidgets.extensions.debounceClickable +import com.navi.naviwidgets.extensions.getBackground +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.naviwidgets.extensions.setWidgetLayoutParams +import com.navi.naviwidgets.models.FooterButtonData +import com.navi.naviwidgets.models.response.CardWithLottieTextButtonWidget + +@Composable +fun CardWithLottieTextButtonWidgetComposable( + widgetData: CardWithLottieTextButtonWidget?, + widgetCallback: WidgetCallback? = null +) { + val data by + remember( + key1 = widgetData, + calculation = { mutableStateOf(widgetData, neverEqualPolicy()) } + ) + setWidgetLayoutParams(widgetLayoutParams = data?.widgetLayoutParams) { + data?.cardWithLottieTextButtonWidgetData?.let { data -> + Box( + modifier = + Modifier.fillMaxWidth() + .getBackground(data.cardBackgroundData) + .debounceClickable( + onClick = { data.cardCta?.let { widgetCallback?.onClick(it) } }, + delayMillis = 600 + ) + ) { + data.cardTag?.text?.let { + NaviTextWidgetized( + textFieldData = data.cardTag, + modifier = Modifier.align(Alignment.TopEnd) + ) + } + data.lottieData?.lottieUrl?.let { + NaviLottie( + lottie = data.lottieData, + modifier = Modifier.align(Alignment.BottomEnd).height(122.dp).width(97.dp) + ) + } + Column( + modifier = + Modifier.padding(start = 16.dp, top = 24.dp, end = 45.dp, bottom = 24.dp) + ) { + data.items?.forEach { itemData -> + Row( + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.Top + ) { + itemData.icon?.url?.let { + NaviImage( + imageFieldData = itemData.icon, + widgetCallback = widgetCallback, + modifier = Modifier.padding(1.dp).size(14.dp) + ) + } + itemData.title?.text?.let { + NaviTextWidgetized( + textFieldData = itemData.title, + widgetCallback = widgetCallback, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + Spacer(modifier = Modifier.padding(bottom = 4.dp)) + } + data.footerButton?.let { + FooterButton( + modifier = + Modifier.padding(top = 16.dp) + .wrapContentWidth() + .wrapContentHeight(), + data = it, + widgetCallback = widgetCallback, + ) + } + } + } + } + } +} + +@Composable +private fun FooterButton( + data: FooterButtonData? = null, + widgetCallback: WidgetCallback? = null, + modifier: Modifier +) { + val buttonBgColor = remember(data?.backgroundColor) { hexToColor(data?.backgroundColor) } + Button( + modifier = modifier, + onClick = debounceClick(onClick = { data?.cta?.let { widgetCallback?.onClick(it) } }), + interactionSource = remember { MutableInteractionSource() }, + enabled = true, + colors = + ButtonDefaults.buttonColors( + disabledContainerColor = buttonBgColor.copy(alpha = 0.56f), + containerColor = buttonBgColor, + ), + shape = RoundedCornerShape(4.dp), + contentPadding = PaddingValues(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 8.dp), + ) { + NaviTextWidgetized( + textFieldData = data?.title, + modifier = Modifier.wrapContentWidth().wrapContentHeight() + ) + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CardWithTitleContentListWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CardWithTitleContentListWidgetComposable.kt new file mode 100644 index 0000000000..91a4f82066 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CardWithTitleContentListWidgetComposable.kt @@ -0,0 +1,76 @@ +/* + * + * * 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.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.extensions.NaviCard +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.getBackground +import com.navi.naviwidgets.extensions.setContentPadding +import com.navi.naviwidgets.extensions.setWidgetLayoutParams +import com.navi.naviwidgets.models.response.CardWithTitleContentListWidget + +@Composable +fun CardWithTitleContentListWidgetComposable( + cardWithTitleContentListWidget: CardWithTitleContentListWidget?, + widgetCallback: WidgetCallback? = null +) { + val data by + remember( + key1 = cardWithTitleContentListWidget, + calculation = { mutableStateOf(cardWithTitleContentListWidget, neverEqualPolicy()) } + ) + setWidgetLayoutParams(widgetLayoutParams = data?.widgetLayoutParams) { + data?.widgetData?.let { data -> + NaviCard(modifier = Modifier.fillMaxWidth(), cardProperties = data.cardProperties) { + Column { + Row( + modifier = + Modifier.fillMaxWidth() + .getBackground(data.titleBackground) + .setContentPadding(data.titleBackground?.contentPadding), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + data.title?.let { title -> + NaviTextWidgetized( + textFieldData = title, + widgetCallback = widgetCallback + ) + } + } + Column( + modifier = + Modifier.fillMaxWidth() + .setContentPadding(data.itemContainerProperties?.contentPadding), + ) { + data.items?.let { items -> + repeat(items.size) { idx -> + NaviTextWidgetized( + textFieldData = items[idx].title, + widgetCallback = widgetCallback + ) + } + } + } + } + } + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CardWithTitleFooterImageWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CardWithTitleFooterImageWidgetComposable.kt new file mode 100644 index 0000000000..b2126deb2b --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/CardWithTitleFooterImageWidgetComposable.kt @@ -0,0 +1,89 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +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.reusable.FooterButtonComposable +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.getBackground +import com.navi.naviwidgets.extensions.setContentPadding +import com.navi.naviwidgets.extensions.setWidgetLayoutParams +import com.navi.naviwidgets.models.response.CardWithTitleFooterImageWidgetData + +@Composable +fun CardWithTitleFooterImageWidgetComposable( + widgetData: CardWithTitleFooterImageWidgetData?, + state: String, + widgetCallback: WidgetCallback? = null, +) { + val data by + remember( + key1 = widgetData, + calculation = { mutableStateOf(widgetData, neverEqualPolicy()) } + ) + setWidgetLayoutParams(widgetLayoutParams = data?.widgetLayoutParams) { + data?.widgetData?.let { data -> + Box(modifier = Modifier.getBackground(data.cardBackgroundData)) { + data.cardImage?.url.let { + NaviImage( + modifier = Modifier.align(Alignment.TopEnd), + imageFieldData = data.cardImage, + widgetCallback = widgetCallback + ) + } + Column { + data.title?.text.let { + NaviTextWidgetized( + textFieldData = data.title, + widgetCallback = widgetCallback, + modifier = Modifier + ) + } + Spacer(modifier = Modifier.height(24.dp)) + Row(modifier = Modifier.padding(start = 16.dp)) { + data.items?.forEach { subTitle -> + NaviTextWidgetized( + textFieldData = subTitle, + widgetCallback = widgetCallback, + modifier = Modifier + ) + } + } + Spacer(modifier = Modifier.height(48.dp)) + data.footerButton.let { + FooterButtonComposable( + modifier = + Modifier.getBackground(data.footerContainerProperties) + .setContentPadding( + data.footerContainerProperties?.contentPadding + ), + data = data.footerButton, + state = state, + widgetCallback = widgetCallback + ) + } + } + } + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/ListWithDropdownWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/ListWithDropdownWidgetComposable.kt new file mode 100644 index 0000000000..68ed979476 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/ListWithDropdownWidgetComposable.kt @@ -0,0 +1,137 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +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.graphicsLayer +import androidx.compose.ui.unit.dp +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.debounceClickable +import com.navi.naviwidgets.extensions.getBackground +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.naviwidgets.extensions.setContentPadding +import com.navi.naviwidgets.extensions.setWidgetLayoutParams +import com.navi.naviwidgets.models.response.DividerSpecsData +import com.navi.naviwidgets.models.response.ItemType +import com.navi.naviwidgets.models.response.ListWithDropdownWidget +import com.navi.naviwidgets.models.response.ListWithDropdownWidgetBody + +@Composable +fun ListWithDropdownWidgetComposable( + listWithDropdownWidget: ListWithDropdownWidget?, + widgetCallback: WidgetCallback? = null, +) { + val data by + remember( + key1 = listWithDropdownWidget, + calculation = { mutableStateOf(listWithDropdownWidget, neverEqualPolicy()) } + ) + setWidgetLayoutParams(widgetLayoutParams = data?.widgetLayoutParams) { + data?.widgetData?.let { data -> + Column(modifier = Modifier.fillMaxWidth()) { + data.items?.let { items -> + repeat(items.size) { + ItemView( + modifier = Modifier.fillMaxWidth(), + showDivider = it != items.size - 1, + dividerSpecs = data.dividerSpecs, + itemData = items[it], + widgetCallback = widgetCallback + ) + } + } + } + } + } +} + +@Composable +fun ItemView( + modifier: Modifier = Modifier, + showDivider: Boolean, + dividerSpecs: DividerSpecsData? = null, + itemData: ListWithDropdownWidgetBody.Item, + widgetCallback: WidgetCallback? = null +) { + var expanded by remember { mutableStateOf(false) } + val rotation by + animateFloatAsState( + targetValue = if (itemData.itemType == ItemType.DROPDOWN && expanded) 180f else 0f, + animationSpec = tween(durationMillis = 200, easing = LinearEasing) + ) + + Column(modifier = modifier.fillMaxWidth()) { + Row( + verticalAlignment = Alignment.Top, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = + modifier + .setContentPadding(itemData.itemProperties?.contentPadding) + .getBackground(itemData.itemProperties) + .fillMaxWidth() + .debounceClickable(onClick = { expanded = !expanded }) + ) { + itemData.title?.let { title -> + NaviTextWidgetized( + textFieldData = title, + widgetCallback = widgetCallback, + modifier = Modifier.weight(1f).padding(end = 16.dp) + ) + } + itemData.rightTitle?.let { rightTitle -> + NaviTextWidgetized( + textFieldData = rightTitle, + widgetCallback = widgetCallback, + modifier = Modifier.graphicsLayer { rotationZ = rotation } + ) + } + } + itemData.listData?.let { listData -> + AnimatedVisibility( + visible = itemData.itemType == ItemType.DROPDOWN && expanded, + enter = expandVertically(), + exit = shrinkVertically() + ) { + Column(modifier = Modifier.fillMaxWidth()) { + repeat(listData.size) { + NaviTextWidgetized( + textFieldData = listData[it], + widgetCallback = widgetCallback + ) + } + } + } + } + if (showDivider) { + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + color = hexToColor(dividerSpecs?.dividerColor), + ) + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/StepsItemListWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/StepsItemListWidgetComposable.kt new file mode 100644 index 0000000000..c2231e7be8 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/StepsItemListWidgetComposable.kt @@ -0,0 +1,107 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.composewidget.widgets + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +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.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +import androidx.compose.runtime.remember +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.design.customview.DashedDivider +import com.navi.naviwidgets.callbacks.WidgetCallback +import com.navi.naviwidgets.extensions.NaviImage +import com.navi.naviwidgets.extensions.NaviTextWidgetized +import com.navi.naviwidgets.extensions.hexToColor +import com.navi.naviwidgets.extensions.setWidgetLayoutParams +import com.navi.naviwidgets.models.response.Item +import com.navi.naviwidgets.models.response.StepsItemListWidget + +@Composable +fun StepsItemListWidgetComposable( + stepsItemListWidget: StepsItemListWidget?, + widgetCallback: WidgetCallback? = null +) { + val data by + remember( + key1 = stepsItemListWidget, + calculation = { mutableStateOf(stepsItemListWidget, neverEqualPolicy()) } + ) + setWidgetLayoutParams(widgetLayoutParams = data?.widgetLayoutParams) { + data?.widgetData?.let { data -> + Column(modifier = Modifier.fillMaxWidth()) { + data.items?.let { items -> + repeat(items.size) { + ItemView( + modifier = Modifier.fillMaxWidth(), + showDivider = it != items.size - 1, + itemData = items[it], + widgetCallback = widgetCallback + ) + } + } + } + } + } +} + +@Composable +private fun ItemView( + modifier: Modifier = Modifier, + showDivider: Boolean, + itemData: Item, + widgetCallback: WidgetCallback? = null +) { + Column() { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier.fillMaxWidth().height(IntrinsicSize.Min) + ) { + NaviImage(imageFieldData = itemData.icon, widgetCallback = widgetCallback) + Spacer(modifier = Modifier.width(12.dp)) + Column { + Row(verticalAlignment = Alignment.CenterVertically) { + NaviTextWidgetized( + textFieldData = itemData.title, + widgetCallback = widgetCallback + ) + itemData.tagTitle?.text?.let { + NaviTextWidgetized( + textFieldData = itemData.tagTitle, + widgetCallback = widgetCallback + ) + } + } + NaviTextWidgetized( + textFieldData = itemData.content, + widgetCallback = widgetCallback + ) + } + } + if (showDivider) { + Row(modifier = Modifier.padding(start = (itemData.icon?.iconWidth.orZero().dp) / 2)) { + DashedDivider( + thicknessInDp = 1.dp, + color = hexToColor(itemData.dividerSpecs?.dividerColor), + modifier = Modifier.height(itemData.dividerSpecs?.dividerHeight.orZero().dp) + ) + } + } + } +} 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 bad25a3c22..e0fbcac4d1 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 @@ -1295,3 +1295,15 @@ fun ShowShimmer(loading: Boolean, context: Context, layoutId: Int) { AndroidView(modifier = Modifier.fillMaxSize().background(Color.White), factory = { layout }) } } + +@Composable +fun debounceClick(onClick: () -> Unit, delayMillis: Long = 600): () -> Unit { + var lastClickTime by remember { mutableStateOf(0L) } + return { + val currentTime = System.currentTimeMillis() + if (currentTime - lastClickTime >= delayMillis) { + lastClickTime = currentTime + onClick() + } + } +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/CardWithLottieTextButtonWidget.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/CardWithLottieTextButtonWidget.kt new file mode 100644 index 0000000000..28b75887c2 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/CardWithLottieTextButtonWidget.kt @@ -0,0 +1,49 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.models.response + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import com.navi.base.model.CtaData +import com.navi.design.utils.BackgroundDrawableData +import com.navi.naviwidgets.models.FooterButtonData +import com.navi.naviwidgets.models.GenericWidgetDataInfo +import com.navi.naviwidgets.models.LottieFieldData +import kotlinx.parcelize.Parcelize + +class CardWithLottieTextButtonWidget( + @SerializedName("widgetData") + val cardWithLottieTextButtonWidgetData: CardWithLottieTextButtonWidgetData? = null +) : + GenericWidgetDataInfo( + widgetId = WIDGET_NAME, + widgetNameForBaseAdapter = WIDGET_NAME, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + companion object { + const val WIDGET_NAME = "CARD_WITH_LOTTIE_TEXT_BUTTON" + } +} + +data class CardWithLottieTextButtonWidgetData( + @SerializedName("cardCta") val cardCta: CtaData? = null, + @SerializedName("cardBackgroundData") val cardBackgroundData: BackgroundDrawableData? = null, + @SerializedName("items") val items: List? = null, + @SerializedName("cardTag") val cardTag: TextFieldData? = null, + @SerializedName("footerButton") val footerButton: FooterButtonData? = null, + @SerializedName("lottieData") val lottieData: LottieFieldData? = null, +) + +@Parcelize +data class CardItemsData( + @SerializedName("icon") val icon: ImageFieldData? = null, + @SerializedName("title") val title: TextFieldData? = null, +) : Parcelable diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/CardWithTitleContentListWidget.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/CardWithTitleContentListWidget.kt new file mode 100644 index 0000000000..954e6a20f6 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/CardWithTitleContentListWidget.kt @@ -0,0 +1,38 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.models.response + +import com.google.gson.annotations.SerializedName +import com.navi.design.utils.BackgroundDrawableData +import com.navi.naviwidgets.models.GenericWidgetDataInfo + +data class CardWithTitleContentListWidget( + @SerializedName("widgetData") val widgetData: CardWithTitleContentListWidgetBody? = null +) : + GenericWidgetDataInfo( + widgetId = WIDGET_NAME, + widgetNameForBaseAdapter = WIDGET_NAME, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + + companion object { + const val WIDGET_NAME = "CARD_WITH_TITLE_CONTENT_LIST_WIDGET" + } +} + +data class CardWithTitleContentListWidgetBody( + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("titleBackground") val titleBackground: BackgroundDrawableData? = null, + @SerializedName("items") val items: List? = null, + @SerializedName("itemContainerProperties") + val itemContainerProperties: BackgroundDrawableData? = null, + @SerializedName("cardProperties") val cardProperties: CardProperties? = null, +) diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/CardWithTitleFooterImageWidgetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/CardWithTitleFooterImageWidgetData.kt new file mode 100644 index 0000000000..101dbe0b93 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/CardWithTitleFooterImageWidgetData.kt @@ -0,0 +1,39 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.models.response + +import com.google.gson.annotations.SerializedName +import com.navi.design.utils.BackgroundDrawableData +import com.navi.naviwidgets.models.FooterButtonData +import com.navi.naviwidgets.models.GenericWidgetDataInfo + +data class CardWithTitleFooterImageWidgetData( + @SerializedName("widgetData") val widgetData: CardWithTitleFooterImageWidgetBody? = null +) : + GenericWidgetDataInfo( + widgetId = WIDGET_NAME, + widgetNameForBaseAdapter = WIDGET_NAME, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + companion object { + const val WIDGET_NAME = "CARD_WITH_TITLE_FOOTER_IMAGE" + } +} + +data class CardWithTitleFooterImageWidgetBody( + @SerializedName("cardBackgroundData") val cardBackgroundData: BackgroundDrawableData? = null, + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("items") val items: List? = null, + @SerializedName("footerButton") val footerButton: FooterButtonData? = null, + @SerializedName("footerContainerProperties") + val footerContainerProperties: BackgroundDrawableData? = null, + @SerializedName("cardImage") val cardImage: ImageFieldData? = null +) diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/ListWithDropdownWidgetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/ListWithDropdownWidgetData.kt new file mode 100644 index 0000000000..b8bd959c13 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/ListWithDropdownWidgetData.kt @@ -0,0 +1,46 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.models.response + +import com.google.gson.annotations.SerializedName +import com.navi.design.utils.BackgroundDrawableData +import com.navi.naviwidgets.models.GenericWidgetDataInfo + +data class ListWithDropdownWidget( + @SerializedName("widgetData") val widgetData: ListWithDropdownWidgetBody? = null +) : + GenericWidgetDataInfo( + widgetId = WIDGET_NAME, + widgetNameForBaseAdapter = WIDGET_NAME, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + companion object { + const val WIDGET_NAME = "LIST_WITH_DROPDOWN_WIDGET" + } +} + +data class ListWithDropdownWidgetBody( + @SerializedName("items") val items: List? = null, + @SerializedName("dividerSpecs") val dividerSpecs: DividerSpecsData? = null +) { + data class Item( + @SerializedName("itemProperties") val itemProperties: BackgroundDrawableData? = null, + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("rightTitle") val rightTitle: TextFieldData? = null, + @SerializedName("itemType") val itemType: ItemType = ItemType.TEXT, + @SerializedName("listData") val listData: List? = null, + ) +} + +enum class ItemType { + TEXT, + DROPDOWN, +} diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/StepsItemListWidgetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/StepsItemListWidgetData.kt new file mode 100644 index 0000000000..a5a4cdff04 --- /dev/null +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/models/response/StepsItemListWidgetData.kt @@ -0,0 +1,38 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.naviwidgets.models.response + +import com.google.gson.annotations.SerializedName +import com.navi.naviwidgets.models.GenericWidgetDataInfo + +data class StepsItemListWidget( + @SerializedName("widgetData") val widgetData: StepsItemListWidgetBody? = null +) : + GenericWidgetDataInfo( + widgetId = WIDGET_NAME, + widgetNameForBaseAdapter = WIDGET_NAME, + isDependentWidget = false, + dependencyWidgetId = null, + isDependencyWidgetShowing = false, + widgetError = null + ) { + + companion object { + const val WIDGET_NAME = "STEPS_ITEM_LIST_WIDGET" + } +} + +data class StepsItemListWidgetBody(@SerializedName("items") val items: List? = null) + +data class Item( + @SerializedName("icon") val icon: ImageFieldData? = null, + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("content") val content: TextFieldData? = null, + @SerializedName("tagTitle") val tagTitle: TextFieldData? = null, + @SerializedName("dividerSpecs") val dividerSpecs: DividerSpecsData? = null +) diff --git a/components/widgets/card-with-icon-widget/CardWithIconWidget.tsx b/components/widgets/card-with-icon-widget/CardWithIconWidget.tsx index 5680990ca5..ae07b3d505 100644 --- a/components/widgets/card-with-icon-widget/CardWithIconWidget.tsx +++ b/components/widgets/card-with-icon-widget/CardWithIconWidget.tsx @@ -24,11 +24,15 @@ const CardWithIconWidget = ({ }; return ( - + {widgetData.title?.text && ( )}