TP-58668 | HL loan tab dashboard (#10395)

This commit is contained in:
Aditya Piyush
2024-04-10 14:32:33 +05:30
committed by GitHub
parent 1a1e2ae945
commit 6ddae79f9d
22 changed files with 903 additions and 13 deletions

View File

@@ -70,6 +70,7 @@ import com.navi.common.video.NaviYoutubeActivity
import com.navi.gold.navigator.NaviDigitalGoldDeeplinkNavigator
import com.navi.gold.ui.IconTitleDescBottomSheet
import com.navi.homeloan.interest_reset.activity.InterestResetActivity
import com.navi.homeloan.dashboard.activity.NewHLDashboardActivity
import com.navi.homeloan.tranche.activity.TrancheDisbursalActivity
import com.navi.insurance.navigator.NaviInsuranceDeeplinkNavigator
import com.navi.insurance.util.VIDEO_ID_EXTRA
@@ -223,6 +224,7 @@ object NaviDeepLinkNavigator : DeepLinkListener {
private const val STATUS_TRACKER = "status_tracker"
const val CUSTOM_PAYMENT = "custom_payment"
private const val INTEREST_RESET = "interest_reset"
private const val NEW_HL_DASHBOARD = "new_hl_dashboard"
const val SEND_EMAIL = "sendEmail"
const val GI = "gi"
const val HOME = "HOME"
@@ -635,6 +637,9 @@ object NaviDeepLinkNavigator : DeepLinkListener {
INTEREST_RESET -> {
intent = Intent(activity, InterestResetActivity::class.java)
}
NEW_HL_DASHBOARD -> {
intent = Intent(activity, NewHLDashboardActivity::class.java)
}
FEEDBACK_SCREEN -> {
intent = Intent(activity, FeedbackActivity::class.java)
}

View File

@@ -98,7 +98,6 @@ import com.naviapp.models.Offer
import com.naviapp.models.RatingData
import com.naviapp.models.RedirectPageStatus
import com.naviapp.models.TrueCallerDropCallData
import com.naviapp.models.UserContact
import com.naviapp.models.UserInstalledApp
import com.naviapp.models.UserProfile
import com.naviapp.models.request.AddressProofRequest

View File

@@ -41,6 +41,7 @@ object Constants {
const val DASHBOARD_INSURANCE_DEEPLINK = "HOME/DASHBOARD/Insurance"
const val DASHBOARD_LOANS_DEEPLINK = "HOME/DASHBOARD/Loans"
const val NAVI_HOME_DEEPLINK = "HOME/HOME"
const val EMPTY = ""
const val REPEAT_INTERVAL_FOR_PERIODIC_WORKER = 365L

View File

@@ -19,6 +19,12 @@
android:screenOrientation="portrait"
android:theme="@style/BaseThemeStyle" />
<activity
android:name=".dashboard.activity.NewHLDashboardActivity"
android:exported="false"
android:screenOrientation="portrait"
android:theme="@style/BaseThemeStyle" />
</application>
</manifest>

View File

@@ -0,0 +1,114 @@
/*
*
* * Copyright © 2022 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.homeloan.common.customview
import android.os.Bundle
import android.view.View
import android.view.ViewStub
import androidx.databinding.DataBindingUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.navi.base.deeplink.DeepLinkManager
import com.navi.common.ui.fragment.BaseBottomSheet
import com.navi.common.utils.toCtaData
import com.navi.homeloan.R
import com.navi.homeloan.common.utils.HLIconUtils
import com.navi.homeloan.common.utils.NaviHLAnalytics.Companion.HL_UNIVERSAL_BOTTOMSHEET
import com.navi.homeloan.databinding.HlUniversalBottomsheetBinding
import com.navi.naviwidgets.extensions.setTextFieldData
import com.navi.naviwidgets.models.response.GenericBottomSheetData
class HLUniversalBottomSheet : BaseBottomSheet() {
private lateinit var binding: HlUniversalBottomsheetBinding
private var data: GenericBottomSheetData? = null
override fun setContainerView(viewStub: ViewStub) {
viewStub.layoutResource = R.layout.hl_universal_bottomsheet
binding = DataBindingUtil.getBinding(viewStub.inflate())!!
data = arguments?.getParcelable(bottomSheetData)
setProperties()
}
private fun makeBottomSheetNonDraggable() {
val behaviour = (dialog as BottomSheetDialog).behavior
behaviour.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_DRAGGING) {
behaviour.state = BottomSheetBehavior.STATE_EXPANDED
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
/*NO OP*/
}
})
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val isBottomSheetDraggable: Boolean = arguments?.getBoolean(IS_BOTTOM_SHEET_DRAGGABLE, true) ?: true
if (dialog is BottomSheetDialog && !isBottomSheetDraggable) {
makeBottomSheetNonDraggable()
}
}
private fun setProperties() {
binding.data = data
binding.apply {
data?.infoMessage?.leftIcon?.iconCode?.let {
messageContainer.visibility = View.VISIBLE
leftIcon.visibility = View.VISIBLE
HLIconUtils.updateIcon(
iconCode = it,
imageView = leftIcon,
)
}
data?.infoMessage?.message?.let {
messageContainer.visibility = View.VISIBLE
tvMessage.visibility = View.VISIBLE
tvMessage.setTextFieldData(it)
}
}
binding.primaryBtn.setOnClickListener {
data?.actionData?.primaryAction?.let {
DeepLinkManager.getDeepLinkListener()
?.navigateTo(this.activity, it.toCtaData(), finish = true)
safelyDismissDialog()
}
}
binding.secondaryBtn.setOnClickListener {
data?.actionData?.secondaryAction?.let {
DeepLinkManager.getDeepLinkListener()
?.navigateTo(this.activity, it.toCtaData(), finish = true)
safelyDismissDialog()
}
}
}
companion object {
private const val IS_BOTTOM_SHEET_DRAGGABLE = "IS_BOTTOM_SHEET_DRAGGABLE"
val bottomSheetData = "bottomSheetData"
val TAG = HL_UNIVERSAL_BOTTOMSHEET
fun getInstance(data: GenericBottomSheetData, isBottomSheetDraggable: Boolean = true): HLUniversalBottomSheet {
return HLUniversalBottomSheet().apply {
val bundle = Bundle().apply {
putParcelable(bottomSheetData, data)
putBoolean(IS_BOTTOM_SHEET_DRAGGABLE, isBottomSheetDraggable)
}
arguments = bundle
}
}
}
override val screenName: String
get() = HL_UNIVERSAL_BOTTOMSHEET
}

View File

@@ -0,0 +1,29 @@
/*
*
* * Copyright © 2022-2023 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.homeloan.common.models
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import com.navi.base.model.ActionData
import com.navi.common.model.common.ExtraDataDetails
import com.navi.naviwidgets.models.NaviWidget
import java.io.Serializable
import kotlinx.parcelize.Parcelize
@Parcelize
data class HLDashboardContentResponse(
@SerializedName("header") val header: List<NaviWidget>? = null,
@SerializedName("contentWidget") val listOfWidgets: List<NaviWidget>? = null,
@SerializedName("extraData") val extraData: ExtraDataDetails? = null,
@SerializedName("metaData") val metaData: DashboardContentMetaData? = null
) : Serializable, Parcelable
@Parcelize
data class DashboardContentMetaData(
@SerializedName("redirectionActionData") val redirectionActionData: ActionData? = null
) : Parcelable

View File

@@ -0,0 +1,22 @@
/*
*
* * Copyright © 2022-2023 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.homeloan.common.models
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class HLDashboardTabsResponse(
@SerializedName("tabs") val tabs: List<HLDashboardTab>? = null,
) : Parcelable
@Parcelize
data class HLDashboardTab(
@SerializedName("name") val name: String
) : Parcelable

View File

@@ -7,13 +7,16 @@
package com.navi.homeloan.common.network.retrofit
import com.navi.common.model.UploadDataAsyncResponse
import com.navi.common.network.models.GenericResponse
import com.navi.homeloan.common.models.HLDashboardContentResponse
import com.navi.homeloan.tranche.models.HLCustomPaymentRequest
import com.navi.homeloan.tranche.models.RequestInstallmentDetails
import com.navi.homeloan.tranche.models.RequestInstallmentResponse
import com.navi.homeloan.tranche.models.TrancheDisbursalResponse
import com.navi.homeloan.tranche.models.TrancheDisbursalResponseV2
import com.navi.naviwidgets.models.response.GenericWidgetResponse
import com.navi.naviwidgets.models.response.HideCardData
import retrofit2.Response
import retrofit2.http.*
@@ -79,4 +82,15 @@ interface RetrofitService {
@Query("flowType") flowType: String?,
@Body customPayRequest: HLCustomPaymentRequest?
): Response<GenericResponse<GenericWidgetResponse>>
@GET("/home-loan/dashboard/post-purchase")
suspend fun fetchHLDashboardContent(
@QueryMap extraParams: Map<String, String>? = emptyMap()
): Response<GenericResponse<HLDashboardContentResponse>>
@POST("/hide-status-card")
suspend fun hideStatusCard2(
@Body hideCardData: HideCardData,
@Header("X-Target") header: String
): Response<GenericResponse<UploadDataAsyncResponse>>
}

View File

@@ -8,6 +8,7 @@
package com.navi.homeloan.common.utils
import androidx.fragment.app.Fragment
import com.navi.homeloan.dashboard.fragments.HLDashboardFragment
import com.navi.homeloan.interest_reset.fragments.HomeLoanEmiCalendarFragment
import com.navi.homeloan.interest_reset.fragments.InterestResetFragment
import com.navi.homeloan.tranche.fragments.DisbursementRequestFragment
@@ -28,6 +29,7 @@ class HLFragmentMapper @Inject constructor() {
DISBURSEMENT_REQUEST_FRAGMENT -> DisbursementRequestFragment()
INTEREST_RESET -> InterestResetFragment()
HOME_LOAN_EMI_CALENDER -> HomeLoanEmiCalendarFragment()
HOME_LOAN_DASHBOARD_FRAGMENT -> HLDashboardFragment()
else -> null
}
}
@@ -41,6 +43,7 @@ class HLFragmentMapper @Inject constructor() {
DISBURSEMENT_REQUEST_FRAGMENT -> DisbursementRequestFragment.TAG
INTEREST_RESET -> InterestResetFragment.TAG
HOME_LOAN_EMI_CALENDER -> HomeLoanEmiCalendarFragment.TAG
HOME_LOAN_DASHBOARD_FRAGMENT -> HLDashboardFragment.TAG
else -> ""
}
}
@@ -53,5 +56,6 @@ class HLFragmentMapper @Inject constructor() {
const val REQUEST_INSTALLMENT_SCREEN_V2 = "request_installment_screen_v2"
const val INTEREST_RESET = "interest_reset_screen"
const val HOME_LOAN_EMI_CALENDER = "custom_payment_calendar_review"
const val HOME_LOAN_DASHBOARD_FRAGMENT = "home_loan_dashboard_fragment"
}
}

View File

@@ -158,6 +158,8 @@ class NaviHLAnalytics private constructor() {
const val HL_HELP_BOTTOMSHEET_OPEN = "HL_Help_BottomSheet_Open"
const val HL_HELP_BOTTOMSHEET_CALL_CLICK = "HL_Help_BottomSheet_Call_Click"
const val SELECTED_OPTION = "selected_option"
const val NEW_HL_DASHBOARD_SCREEN = "new_hl_dashboard_screen"
const val HL_UNIVERSAL_BOTTOMSHEET = "HL_Universal_BottomSheet"
const val METADATA_ANALYTICS_EVENT = "analyticsEvent"
}
}

View File

@@ -1,12 +1,15 @@
package com.navi.homeloan.common.utils
import android.text.format.DateFormat
import com.google.gson.Gson
import com.navi.base.utils.isNull
import com.navi.common.CommonLibManager
import com.navi.common.model.ModuleName
import com.navi.common.model.NetworkInfo
import com.navi.common.utils.EMPTY
import com.navi.common.utils.log
import com.navi.homeloan.common.Constants.API_CONNECT_TIMEOUT_VALUE
import com.navi.homeloan.common.models.HLDashboardContentResponse
import java.util.Calendar
import java.util.Locale
@@ -58,4 +61,16 @@ fun getAmount(amount: String): Double {
fun getAmountAsString(amount: String): String {
return String.format("%.2f",stringToDouble(amount.trim().replace(",", EMPTY)))
}
fun isSameDashboardResponse(
newResponse: HLDashboardContentResponse?,
oldResponse: HLDashboardContentResponse?
): Boolean {
if (newResponse.isNull() || oldResponse.isNull()) return false
val gson = Gson()
return gson
.toJson(newResponse?.listOfWidgets)
.equals(gson.toJson(oldResponse?.listOfWidgets)) &&
gson.toJson(newResponse?.metaData).equals(gson.toJson(oldResponse?.metaData))
}

View File

@@ -0,0 +1,73 @@
/*
*
* * Copyright © 2022-2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.homeloan.dashboard.activity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.navi.base.deeplink.DeepLinkManager
import com.navi.base.model.CtaData
import com.navi.common.listeners.FragmentInterchangeListener
import com.navi.common.model.ModuleNameV2
import com.navi.common.ui.activity.BaseActivity
import com.navi.common.utils.Constants.NAVI_HOME_DEEPLINK
import com.navi.homeloan.R
import com.navi.homeloan.common.utils.HLFragmentMapper
import com.navi.homeloan.common.utils.NaviHLAnalytics
import com.navi.homeloan.databinding.ActivityNewHlDashboardBinding
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class NewHLDashboardActivity : BaseActivity(), FragmentInterchangeListener {
private lateinit var binding: ActivityNewHlDashboardBinding
@Inject
lateinit var fragmentMapper: HLFragmentMapper
override fun onCreate(savedInstanceState: Bundle?) {
binding = DataBindingUtil.setContentView(this, R.layout.activity_new_hl_dashboard)
super.onCreate(savedInstanceState)
navigateToNextScreen(
"home_loan_dashboard_fragment",
intent?.extras ?: Bundle()
)
}
override val screenName: String
get() = NaviHLAnalytics.NEW_HL_DASHBOARD_SCREEN
override val moduleName: ModuleNameV2
get() = ModuleNameV2.COMMON
override fun navigateToNextScreen(currentScreenTag: String, bundle: Bundle) {
val tag = fragmentMapper.getFragmentTagByUrl(currentScreenTag)
val fragment =
supportFragmentManager.findFragmentByTag(tag)
?: fragmentMapper.getFragmentByUrl(currentScreenTag)
fragment?.apply { arguments = bundle }
if (fragment != null) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
if (!supportFragmentManager.isStateSaved && !supportFragmentManager.isDestroyed) {
fragmentTransaction.addToBackStack(tag)
fragmentTransaction.replace(R.id.fragment_container, fragment, tag)
fragmentTransaction.commit()
}
} else {
DeepLinkManager.getDeepLinkListener()
?.navigateTo(this, CtaData(url = currentScreenTag), true)
}
}
override fun onActivityCompleted() {}
override fun onBackPressed() {
DeepLinkManager.getDeepLinkListener()
?.navigateTo(this, CtaData(url = NAVI_HOME_DEEPLINK), true)
}
}

View File

@@ -0,0 +1,268 @@
/*
*
* * Copyright © 2022-2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.homeloan.dashboard.fragments
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.gson.Gson
import com.navi.analytics.utils.NaviTrackEvent
import com.navi.base.deeplink.DeepLinkManager
import com.navi.base.model.CtaData
import com.navi.base.model.CtaType
import com.navi.base.model.NaviClickAction
import com.navi.base.utils.isNull
import com.navi.common.listeners.FragmentInterchangeListener
import com.navi.common.ui.activity.BaseActivity
import com.navi.common.ui.fragment.GratificationFragment
import com.navi.common.utils.TemporaryStorageHelper
import com.navi.common.utils.observeNonNull
import com.navi.common.utils.replaceLayout
import com.navi.common.utils.toCtaData
import com.navi.homeloan.R
import com.navi.homeloan.common.base.HLBaseFragment
import com.navi.homeloan.common.customview.HLUniversalBottomSheet
import com.navi.homeloan.common.models.HLDashboardTab
import com.navi.homeloan.common.utils.NaviHLAnalytics
import com.navi.homeloan.dashboard.viewmodels.HLDashboardFragmentVM
import com.navi.homeloan.databinding.FragmentHlDashboardBinding
import com.navi.naviwidgets.actions.InformationWidgetCloseClickAction
import com.navi.naviwidgets.actions.NavigateClickAction
import com.navi.naviwidgets.actions.ShowBottomSheetClicked
import com.navi.naviwidgets.adapters.NaviInputWidgetAdapter
import com.navi.naviwidgets.callbacks.WidgetCallback
import com.navi.naviwidgets.models.NaviWidget
import com.navi.naviwidgets.utils.getFilteredNaviWidgets
import com.navi.naviwidgets.viewholder.DashboardHolderFactoryImpl
import com.navi.naviwidgets.widgets.BaseNaviWidgetLayout
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class HLDashboardFragment : HLBaseFragment(), WidgetCallback {
private lateinit var binding: FragmentHlDashboardBinding
private val naviAdapter =
NaviInputWidgetAdapter<NaviWidget, Any>(
widgetCallback = this,
factory = DashboardHolderFactoryImpl()
)
private var dashboardTab: HLDashboardTab? = null
private val viewModel by lazy {
ViewModelProvider(this)[HLDashboardFragmentVM::class.java]
}
override val screenName: String
get() = TAG
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding =
DataBindingUtil.inflate(
inflater,
R.layout.fragment_hl_dashboard,
container,
false
)
return binding.root
}
override fun onResume() {
super.onResume()
if (viewModel.dashboardContentResponse.value.isNull() || TemporaryStorageHelper.isDataModified(
dashboardTab?.name.orEmpty()
)
) {
loadScreen(true)
} else {
loadScreen(false)
}
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if(hidden.not()) {
refreshData()
}
}
private fun refreshData() {
if (TemporaryStorageHelper.isDataModified(dashboardTab?.name.orEmpty())) {
loadScreen(true)
} else {
loadScreen(false)
}
}
private fun loadScreen(showLoader: Boolean, forceRefresh: Boolean = false) {
if (viewModel.dashboardContentResponse.value.isNull()) {
binding.rvProducts.isVisible = false
showShimmer()
viewModel.fetchHLDashboardContent(true, dashboardTab?.name.orEmpty(), forceRefresh, queryMap)
} else {
if (showLoader) {
showLoader()
}
viewModel.fetchHLDashboardContent(showLoader, dashboardTab?.name.orEmpty(), forceRefresh, queryMap)
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
fragmentInterchangeListener = context as? FragmentInterchangeListener
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
initObserver()
}
private fun initUI() {
binding.sflShimmer.startShimmer()
setUpAdapter()
}
private fun setUpAdapter() {
binding.rvProducts.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = naviAdapter
}
}
private fun initObserver() {
viewModel.dashboardContentResponse.observeNonNull(viewLifecycleOwner) { dashboardContentResponse ->
hideLoader()
TemporaryStorageHelper.updateApiTs(dashboardTab?.name.orEmpty())
dashboardContentResponse?.header?.getOrNull(0)?.let { updateContainer(it, binding.headerContainer) }
binding.rvProducts.isVisible = true
getFilteredNaviWidgets(dashboardContentResponse?.listOfWidgets)?.let { listOfWidgets ->
hideShimmer()
naviAdapter.setData(listOfWidgets)
}
dashboardContentResponse?.metaData?.redirectionActionData?.let {
DeepLinkManager.getDeepLinkListener()
?.navigateTo(activity, it.toCtaData(), finish = true)
}
dashboardContentResponse?.extraData?.screenDefinition?.let { screenDefinition ->
val gratificationFragment =
GratificationFragment.getInstance(
gratificationData = Gson().toJson(screenDefinition),
screenName = screenName
)
(activity as? BaseActivity)?.safelyOpenFragment(
gratificationFragment,
GratificationFragment.TAG
)
}
}
viewModel.hidePaymentStatus.observeNonNull(viewLifecycleOwner) {
hideLoader()
loadScreen(false)
}
viewModel.cachedResponse.observe(viewLifecycleOwner) {
hideLoader()
hideShimmer()
TemporaryStorageHelper.updateApiTs(dashboardTab?.name.orEmpty())
binding.rvProducts.isVisible = true
}
viewModel.errorResponse.observe(viewLifecycleOwner) {
binding.rvProducts.isVisible = false
hideShimmer()
}
}
private fun updateContainer(
naviWidget: NaviWidget,
container: ViewGroup
) {
viewLifecycleOwner.lifecycleScope.launch {
val layoutBinding =
container.replaceLayout(widgetIdProvider.getNaviWidgetLayoutId(naviWidget))
(layoutBinding?.root as? BaseNaviWidgetLayout)?.updateLayout(
layoutBinding,
naviWidget,
this@HLDashboardFragment
)
}
}
override fun onClick(naviClickAction: NaviClickAction, widgetId: String?) {
when (naviClickAction) {
is InformationWidgetCloseClickAction -> {
showLoader()
NaviHLAnalytics.naviHLAnalytics.sendAnalyticsEvent(
analyticsEvent = naviClickAction.analyticsEventProperties
)
naviClickAction.hideCardData?.let { viewModel.hideStatusCard2(it) }
}
is ShowBottomSheetClicked -> {
val bottomSheet =
HLUniversalBottomSheet.getInstance(naviClickAction.genericBottomSheetData, isBottomSheetDraggable = false)
safelyShowBottomSheet(bottomSheet, HLUniversalBottomSheet.TAG)
}
is CtaData -> {
when (naviClickAction.url) {
CtaType.GO_BACK.name -> {
activity?.onBackPressed()
}
CtaType.HELP_BOTTOM_SHEET.name -> {
openHelpScreen(screenName)
}
else -> {
val bundle = Bundle()
NaviHLAnalytics.naviHLAnalytics.sendAnalyticsEvent(
naviClickAction.analyticsEventProperties
)
naviClickAction.parameters?.forEach { bundle.putString(it.key, it.value) }
naviClickAction.url?.let {
fragmentInterchangeListener?.navigateToNextScreen(it, bundle)
}
}
}
}
is NavigateClickAction -> {
NaviTrackEvent.setStartTs(screenName)
DeepLinkManager.getDeepLinkListener()
?.navigateTo(activity, CtaData(
url = naviClickAction.url,
parameters = naviClickAction.parameters
), false)
}
}
}
private fun showShimmer() {
binding.sflShimmer.apply {
startShimmer()
visibility = View.VISIBLE
}
}
private fun hideShimmer() {
binding.sflShimmer.apply {
stopShimmer()
visibility = View.GONE
}
}
companion object {
const val TAG = "hlDashboardFragment"
}
}

View File

@@ -0,0 +1,29 @@
/*
*
* * Copyright © 2022-2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.homeloan.dashboard.repository
import com.navi.common.model.ModuleName
import com.navi.common.network.retrofit.ResponseCallback
import com.navi.homeloan.common.network.retrofit.RetrofitService
import com.navi.naviwidgets.models.response.HideCardData
import javax.inject.Inject
class HLDashboardFragmentRepository @Inject constructor(private val retrofitService: RetrofitService) :
ResponseCallback() {
suspend fun fetchHLDashboardContent(queryMap: HashMap<String, String>) =
apiResponseCallback(retrofitService.fetchHLDashboardContent(queryMap))
suspend fun hideStatusCard2(hideCardData: HideCardData) =
apiResponseCallback(
retrofitService.hideStatusCard2(
hideCardData,
hideCardData.moduleName ?: ModuleName.HL.name
)
)
}

View File

@@ -0,0 +1,90 @@
/*
*
* * Copyright © 2022-2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.homeloan.dashboard.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.navi.base.utils.isNotNull
import com.navi.common.model.UploadDataAsyncResponse
import com.navi.common.utils.TemporaryStorageHelper
import com.navi.common.viewmodel.BaseVM
import com.navi.homeloan.common.models.HLDashboardContentResponse
import com.navi.homeloan.common.utils.isSameDashboardResponse
import com.navi.homeloan.dashboard.repository.HLDashboardFragmentRepository
import com.navi.naviwidgets.models.response.HideCardData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class HLDashboardFragmentVM
@Inject
constructor(
private val dashboardContentRepository: HLDashboardFragmentRepository,
) : BaseVM() {
private val _dashboardContentResponse = MutableLiveData<HLDashboardContentResponse?>()
val dashboardContentResponse: LiveData<HLDashboardContentResponse?>
get() = _dashboardContentResponse
private val _hidePaymentStatus = MutableLiveData<UploadDataAsyncResponse?>()
val hidePaymentStatus: LiveData<UploadDataAsyncResponse?>
get() = _hidePaymentStatus
/**
* We are making same api call on HomeVM too, If you want to modify this api please do same on HomeVM too
*/
fun fetchHLDashboardContent(showLoader: Boolean, product: String, forceRefresh: Boolean = false, queryMap: HashMap<String, String>) {
coroutineScope.launch(Dispatchers.IO) {
if(forceRefresh) {
TemporaryStorageHelper.clearResponse(product)
}
val isDataModified = TemporaryStorageHelper.isDataModified(product)
if (TemporaryStorageHelper.getApiResponse<HLDashboardContentResponse>(product)
.isNotNull()
) {
_dashboardContentResponse.postValue(
TemporaryStorageHelper.getApiResponse<HLDashboardContentResponse>(product)
)
TemporaryStorageHelper.clearResponse(product)
if (isDataModified.not())
return@launch
}
val response = dashboardContentRepository.fetchHLDashboardContent(queryMap)
if (response.error == null && response.errors.isNullOrEmpty()) {
if (showLoader.not() && isSameDashboardResponse(response.data, _dashboardContentResponse.value)) {
cachedResponse.postValue(true)
} else {
_dashboardContentResponse.postValue(response.data)
}
} else {
launch(Dispatchers.Main) {
setErrorData(
response.errors,
response.error,
cancelable = false,
showFullScreenError = true
)
}
}
}
}
fun hideStatusCard2(hideCardData: HideCardData) {
coroutineScope.launch {
val response = dashboardContentRepository.hideStatusCard2(hideCardData)
if (response.error == null && response.errors.isNullOrEmpty()) {
_hidePaymentStatus.value = response.data
} else {
setErrorData(response.errors, response.error)
}
}
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
~
~ Copyright ©2022 by Navi Technologies Private Limited
~ All rights reserved. Strictly confidential
~
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ /*
~ *
~ * * Copyright © 2022 by Navi Technologies Private Limited
~ * * All rights reserved. Strictly confidential
~ *
~ */
-->
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:shimmer="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".home.dashboard.ui.ProductFragment">
<FrameLayout
android:id="@+id/headerContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
tools:layout_height="@dimen/dp_100" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/headerContainer">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvProducts"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.core.widget.NestedScrollView>
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/sflShimmer"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
shimmer:duration="@integer/integer_800">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/dashboard_content_shimmer" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<FrameLayout
android:id="@+id/flError"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.navi.design.font.FontWeightEnum" />
<variable
name="data"
type="com.navi.naviwidgets.models.response.GenericBottomSheetData" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/dp_16"
android:paddingEnd="@dimen/dp_16"
android:paddingBottom="@dimen/dp_32">
<com.navi.design.textview.NaviTextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/dp_26"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:setTextWithStyle="@{data.title}"
tools:text="Covered members" />
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@id/message_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_goneMarginTop="@dimen/dp_4"
app:setRowDataV2="@{data.items}" />
<LinearLayout
android:id="@+id/message_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/dp_16"
android:layout_marginEnd="@dimen/_16dp"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/container"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/leftIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_5"
android:visibility="gone"
tools:src="@drawable/ic_info_icon_black" />
<com.navi.design.textview.NaviTextView
android:id="@+id/tvMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_8"
android:gravity="start"
android:lineSpacingExtra="2dp"
tools:text="Your First EMI is higher than expected as the interest from loan disbursal date till the first EMI due date has been adjusted in the first EMI for your convenience"
android:textColor="#444444"
android:visibility="gone"
android:textSize="@dimen/layout_dp_14" />
</LinearLayout>
<com.navi.design.textview.NaviTextView
android:id="@+id/primary_btn"
android:layout_width="@dimen/dp_0"
android:layout_height="@dimen/dp_48"
android:layout_marginTop="@dimen/dp_8"
app:setFontStyle="@{FontWeightEnum.TT_MEDIUM}"
style="@style/ButtonFontStylePurple"
android:gravity="center"
android:textSize="@dimen/sp_14"
app:layout_constraintEnd_toStartOf="@id/secondary_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/message_container"
app:setFooterData="@{data.actionData.primaryAction}"
tools:text="Cancel" />
<com.navi.design.textview.NaviTextView
android:id="@+id/secondary_btn"
android:layout_width="@dimen/dp_0"
android:layout_height="@dimen/dp_48"
android:layout_marginStart="@dimen/dp_8"
android:layout_marginTop="@dimen/dp_8"
app:setFontStyle="@{FontWeightEnum.TT_MEDIUM}"
style="@style/ButtonFontStyleGreyBlack"
android:gravity="center"
android:textSize="@dimen/sp_14"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/primary_btn"
app:layout_constraintTop_toBottomOf="@id/message_container"
app:setFooterData="@{data.actionData.secondaryAction}"
tools:text="Pay Now" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -32,6 +32,7 @@ import com.navi.naviwidgets.databinding.RowKeyValueBinding
import com.navi.naviwidgets.databinding.ViewBulletPointsBinding
import com.navi.naviwidgets.extensions.setImageFieldData
import com.navi.naviwidgets.extensions.setTextFieldData
import com.navi.naviwidgets.models.response.GenericBottomSheetLineData
import com.navi.naviwidgets.models.response.ImageFieldData
import com.navi.naviwidgets.models.response.KeyValue
import com.navi.naviwidgets.models.response.TextFieldData
@@ -230,4 +231,18 @@ object BindingAdapterUtil {
fun setProgressWithTimerData(view: ProgressWithTimerView, timerObj: TimerObj?) {
view.setData(timerObj)
}
@BindingAdapter("setRowDataV2")
@JvmStatic
fun setRowDataV2(linearLayout: LinearLayout, items: List<GenericBottomSheetLineData>?) {
linearLayout.removeAllViews()
items?.forEach {
val inflater = LayoutInflater.from(linearLayout.context)
val view = inflater.inflate(R.layout.row_key_value, linearLayout, false)
val binding: RowKeyValueBinding? = DataBindingUtil.bind(view)
binding?.key?.setSpannableString(it.key)
binding?.value?.setSpannableString(it.value)
linearLayout.addView(binding?.root)
}
}
}

View File

@@ -182,7 +182,7 @@ constructor(
if (info.lineData().isNotNull()) {
R.drawable.bg_border_grey_white_color_rect
} else {
R.drawable.bg_full_border_grey_white_fill_color_botton_rounded
R.drawable.bg_border_grey_fill_bottom_rounded_4
}
)
@@ -192,7 +192,7 @@ constructor(
if (info.lineData()?.footerText.isNotNull()) {
R.drawable.bg_border_grey_white_color_rect
} else {
R.drawable.bg_full_border_grey_white_fill_color_botton_rounded
R.drawable.bg_border_grey_fill_bottom_rounded_4
}
)

View File

@@ -18,7 +18,7 @@
android:id="@+id/tvHeaderTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_border_grey_white_fill_color_top_rounded"
android:background="@drawable/bg_border_yellow_fill_top_rounded"
android:fontFamily="@font/tt_semi_bold"
android:gravity="center"
android:letterSpacing="0.16"
@@ -46,7 +46,7 @@
android:id="@+id/clContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_border_grey_white_fill_color_bottom_rounded"
android:background="@drawable/bg_border_grey_fill_bottom_rounded_4"
android:paddingStart="@dimen/dp_16"
app:layout_constraintBottom_toTopOf="@id/clLineData"
app:layout_constraintEnd_toEndOf="parent"
@@ -69,7 +69,7 @@
android:textSize="@dimen/font_mini"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:backgroundTint="#e92c2c"
tools:backgroundTint="#1F002A"
tools:text="2 DAYS OVERDUE" />
<com.navi.design.textview.NaviTextView
@@ -197,7 +197,7 @@
android:layout_marginTop="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_16"
android:layout_marginBottom="@dimen/dp_16"
android:background="@drawable/rounded_positive_button_bg"
android:background="@drawable/bg_cta_primary_purple"
android:fontFamily="@font/tt_medium"
android:gravity="center"
android:textColor="@color/white"
@@ -216,10 +216,10 @@
android:layout_marginTop="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_8"
android:layout_marginBottom="@dimen/dp_16"
android:background="@drawable/rounded_secondary_button_bg"
android:background="@drawable/bg_grey_rounded_4"
android:fontFamily="@font/tt_medium"
android:gravity="center"
android:textColor="@color/title_text_red_color"
android:textColor="@color/cta_purple_color"
android:textSize="@dimen/font_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/tvPrimaryAction"
@@ -233,7 +233,7 @@
android:id="@+id/clLineData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_full_border_grey_white_fill_color_botton_rounded"
android:background="@drawable/bg_border_grey_fill_bottom_rounded_4"
android:paddingBottom="@dimen/dp_16"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
@@ -254,7 +254,7 @@
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dp_30"
android:fontFamily="@font/tt_medium"
android:textColor="@color/title_text_red_color"
android:textColor="@color/cta_purple_color"
android:textSize="@dimen/font_small"
android:paddingStart="@dimen/dp_20"
android:layout_marginTop="@dimen/dp_16"
@@ -280,7 +280,7 @@
android:id="@+id/clBottomBanner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_grey_border_grey_bottom_rounded"
android:background="@drawable/bg_border_grey_fill_bottom_rounded_4"
android:paddingStart="@dimen/dp_20"
android:paddingTop="@dimen/dp_10"
android:paddingEnd="@dimen/dp_16"

View File

@@ -43,7 +43,7 @@
android:id="@+id/selectedTab"
android:layout_width="@dimen/dp_26"
android:layout_height="@dimen/dp_6"
android:background="@drawable/bg_red_rounded_4" />
android:background="@drawable/rounded_purple_button" />
</LinearLayout>
</com.navi.naviwidgets.widgets.ParentContainerWidgetLayout>