[PS] fix scroll state of top navi widget (#8823)

This commit is contained in:
Prakhar Saxena
2023-12-12 12:01:12 +05:30
committed by GitHub
parent 7f6d422f39
commit baff62fb35
5 changed files with 259 additions and 177 deletions

View File

@@ -25,6 +25,7 @@ import com.navi.amc.utils.Constant.WHITE
import com.navi.common.utils.isValidResponse
import com.navi.naviwidgets.models.ImageHeaderWidget
import com.navi.naviwidgets.models.response.HomeProductWidget
import com.navi.naviwidgets.models.response.TopProductWidget
import com.navi.naviwidgets.models.response.amc.AmcPortfolioInformationWidget
import com.navi.naviwidgets.models.response.amc.ListOverlappingHeaderWidget
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -157,7 +158,7 @@ class FundListViewModel @Inject constructor(private val repository: FundListRepo
it as? FundListResponse
}?.let { cachedResponse ->
setFetchingFromRemote(false)
_fundListDataV2.postValue(cachedResponse)
_fundListDataV2.postValue(restoreScrollState(cachedResponse))
return true
}
return false
@@ -175,7 +176,7 @@ class FundListViewModel @Inject constructor(private val repository: FundListRepo
val response = repository.fetchFundsDataV2(fundCategory)
setFetchingFromRemote(false)
if (response.isValidResponse()) {
_fundListDataV2.postValue(response.data)
_fundListDataV2.postValue(restoreScrollState(response.data))
AmcTaskManager.SOFT_REF_CACHE.add(
key = taskName,
value = response.data as Any,
@@ -194,7 +195,7 @@ class FundListViewModel @Inject constructor(private val repository: FundListRepo
it as? FundListResponse
}?.let { cachedResponse ->
setFetchingFromRemote(false)
_fundListDataV2.postValue(cachedResponse)
_fundListDataV2.postValue(restoreScrollState(cachedResponse))
return true
}
return false
@@ -224,13 +225,29 @@ class FundListViewModel @Inject constructor(private val repository: FundListRepo
maxConsumptions = response.data?.cacheConfig?.maxConsumptions ?: AmcTaskManager.DEFAULT_MAX_CONSUMPTIONS
)
}
_fundListDataV2.postValue(response.data)
_fundListDataV2.postValue(restoreScrollState(response.data))
} else {
setErrorData(response.errors, response.error)
}
}
}
private fun restoreScrollState(response: FundListResponse?): FundListResponse? {
response?.listOfWidgets
?.filterIsInstance<TopProductWidget>()
?.firstOrNull()
?.widgetData
?.content
?.scrollState =
_fundListDataV2.value?.listOfWidgets
?.filterIsInstance<TopProductWidget>()
?.firstOrNull()
?.widgetData
?.content
?.scrollState
return response
}
fun containsHomeProductWidget(): Boolean {
return _fundListDataV2.value?.listOfWidgets?.singleOrNull { it.widgetNameForBaseAdapter == HomeProductWidget.WIDGET_NAME } != null
}

View File

@@ -0,0 +1,209 @@
package com.navi.naviwidgets.adapters
import android.graphics.Color
import android.graphics.drawable.LayerDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.navi.base.utils.isNotNullAndNotEmpty
import com.navi.base.utils.isValidIndex
import com.navi.base.utils.orFalse
import com.navi.design.R
import com.navi.design.utils.CornerRadius
import com.navi.design.utils.GradientOrientation
import com.navi.design.utils.deviceWidth
import com.navi.design.utils.dpToPx
import com.navi.design.utils.dpToPxInInt
import com.navi.design.utils.getNaviDrawable
import com.navi.design.utils.parseColorSafe
import com.navi.design.utils.setSpannableString
import com.navi.naviwidgets.callbacks.WidgetCallback
import com.navi.naviwidgets.databinding.TopProductItemBinding
import com.navi.naviwidgets.models.response.TopProductItem
import com.navi.naviwidgets.utils.NaviWidgetIconUtils
class TopProductAdapter(
var items: List<TopProductItem>,
var widgetCallback: WidgetCallback
) :
RecyclerView.Adapter<TopProductAdapter.TopProductVH>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopProductVH {
val binding = TopProductItemBinding.inflate(LayoutInflater.from(parent.context))
binding.root.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
return TopProductVH(binding)
}
override fun onBindViewHolder(holder: TopProductVH, position: Int) {
if (!isValidIndex(position, itemCount))
return
holder.bind(items[position], position, items.size, widgetCallback)
}
override fun getItemCount(): Int {
return items.size
}
class TopProductVH(val itemBinding: TopProductItemBinding) :
RecyclerView.ViewHolder(itemBinding.root) {
fun bind(
widget: TopProductItem,
position: Int,
totalItems: Int,
widgetCallback: WidgetCallback
) {
itemBinding.productLabel.apply {
background = getNaviDrawable(
radii = CornerRadius(
0f,
0f,
dpToPx(4),
dpToPx(4)
),
backgroundColor = widget.label?.bgColor.parseColorSafe()
)
setSpannableString(widget.label?.title)
}
itemBinding.productTitle.setSpannableString(widget.title)
itemBinding.productSubtitle.setSpannableString(widget.subTitle)
itemBinding.productDescription.setSpannableString(widget.description)
widget.imageDetails?.let {
it.height?.let {
itemBinding.productImage.layoutParams.height = dpToPxInInt(it)
}
it.width?.let {
itemBinding.productImage.layoutParams.width = dpToPxInInt(it)
}
widget.imageMargin?.let { margin ->
val lp = itemBinding.productImage.layoutParams as? ViewGroup.MarginLayoutParams
lp?.bottomMargin = dpToPxInInt(margin.bottomDp.toInt())
lp?.marginEnd = dpToPxInInt(margin.endDp.toInt())
itemBinding.productImage.layoutParams = lp
}
itemBinding.productImage.requestLayout()
it.iconCode?.let {
NaviWidgetIconUtils.updateIcon(it, itemBinding.productImage)
}
} ?: kotlin.run {
widget.iconCode?.let {
NaviWidgetIconUtils.updateIcon(it, itemBinding.productImage)
}
}
widget.rightIconDetails?.let {
it.height?.let { height ->
itemBinding.rightArrowIv.layoutParams.height = dpToPxInInt(height)
}
it.width?.let { width ->
itemBinding.rightArrowIv.layoutParams.width = dpToPxInInt(width)
}
itemBinding.rightArrowIv.requestLayout()
it.iconCode?.let { iconCode ->
NaviWidgetIconUtils.updateIcon(iconCode, itemBinding.rightArrowIv)
}
} ?: kotlin.run {
widget.rightArrowIconCode?.let {
NaviWidgetIconUtils.updateIcon(it, itemBinding.rightArrowIv)
}
}
widget.subtitleTopMargin?.let {
val lp = itemBinding.productSubtitle.layoutParams as? ConstraintLayout.LayoutParams
lp?.topMargin = dpToPx(it).toInt()
itemBinding.productSubtitle.layoutParams = lp
}
widget.footerTitle?.let {
itemBinding.productStrategyTv.setSpannableString(widget.footerTitle)
widget.footerIconCode?.let {
NaviWidgetIconUtils.updateIcon(it, itemBinding.productStrategyIv)
}
val footerBg = getNaviDrawable(
radii = CornerRadius(
leftBottom = dpToPx(4),
rightBottom = dpToPx(4)
),
strokeColor = if (widget.requiresStroke.orFalse()) ContextCompat.getColor(
itemView.context,
R.color.border_grey_color
) else Color.TRANSPARENT,
strokeWidth = dpToPx(1).toInt(),
backgroundColor = if (widget.footerBgColor.isNotNullAndNotEmpty())
widget.footerBgColor.parseColorSafe() else Color.TRANSPARENT
)
val footerDrawable = LayerDrawable(arrayOf(footerBg))
footerDrawable.setLayerInset(0, 0, -dpToPxInInt(1), 0, 0)
itemBinding.productStrategyContainer.background = footerDrawable
} ?: kotlin.run {
itemBinding.productStrategyContainer.visibility = View.GONE
itemBinding.divider.visibility = View.GONE
}
val colors = mutableListOf<Int>()
widget.gradient?.let {
it.startGradientColor?.let { colors.add(Color.parseColor(it)) }
it.middleGradientColor?.let { colors.add(Color.parseColor(it)) }
it.endGradientColor?.let { colors.add(Color.parseColor(it)) }
}
itemBinding.productCard.background = getNaviDrawable(
cornerRadius = dpToPx(4).toInt(),
strokeColor = if (widget.requiresStroke.orFalse()) ContextCompat.getColor(
itemView.context,
R.color.border_grey_color
) else Color.TRANSPARENT,
strokeWidth = dpToPx(1).toInt(),
backgroundColor = if (widget.bgColor.isNotNullAndNotEmpty())
widget.bgColor.parseColorSafe() else Color.TRANSPARENT,
gradientColors = widget.gradient?.let { colors.toIntArray() },
gradientOrientation = GradientOrientation.valueOf(
widget.gradient?.orientation ?: GradientOrientation.LEFT_RIGHT.name
)
)
if (totalItems == 1) {
val layoutParams = LinearLayout.LayoutParams(
deviceWidth() - itemView.context.resources.getDimension(R.dimen.dp_32).toInt(),
if (widget.cardHeight != null) dpToPx(widget.cardHeight).toInt()
else itemView.context.resources.getDimension(R.dimen.dp_148).toInt()
)
layoutParams.rightMargin = dpToPx(16).toInt()
layoutParams.topMargin = dpToPx(10).toInt()
layoutParams.leftMargin = dpToPx(16).toInt()
layoutParams.bottomMargin = dpToPx(10).toInt()
itemBinding.widgetContainer.layoutParams = layoutParams
} else if (position == totalItems - 1) {
val layoutParams = LinearLayout.LayoutParams(
(deviceWidth() * 0.77).toInt(),
if (widget.cardHeight != null) dpToPx(widget.cardHeight).toInt()
else itemView.context.resources.getDimension(R.dimen.dp_148).toInt()
)
layoutParams.rightMargin = dpToPx(16).toInt()
layoutParams.topMargin = dpToPx(10).toInt()
layoutParams.leftMargin = dpToPx(16).toInt()
layoutParams.bottomMargin = dpToPx(10).toInt()
itemBinding.widgetContainer.layoutParams = layoutParams
} else {
val layoutParams = LinearLayout.LayoutParams(
(deviceWidth() * 0.77).toInt(),
if (widget.cardHeight != null) dpToPxInInt(widget.cardHeight)
else itemView.context.resources.getDimension(R.dimen.dp_148).toInt()
)
layoutParams.topMargin = dpToPx(10).toInt()
layoutParams.leftMargin = dpToPx(16).toInt()
layoutParams.bottomMargin = dpToPx(10).toInt()
itemBinding.widgetContainer.layoutParams = layoutParams
}
itemBinding.root.setOnClickListener {
widget.actionData?.let { actionData ->
widgetCallback.widgetAnalytics(actionData.metaData?.clickedData)
widgetCallback.onClick(actionData)
}
}
}
}
}

View File

@@ -7,6 +7,7 @@
package com.navi.naviwidgets.models.response
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import com.navi.base.model.ActionData
import com.navi.base.model.ImageDetails
@@ -37,6 +38,7 @@ data class TopProductWidgetData(
data class TopProductWidgetContent(
@SerializedName("actionData") val actionData: ActionData? = null,
@SerializedName("items") val items: List<TopProductItem>? = null,
var scrollState: Parcelable? = null,
)
data class TopProductWidgetHeader(

View File

@@ -7,32 +7,26 @@
package com.navi.naviwidgets.widgets
import com.navi.design.R as DesignR
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.LayerDrawable
import android.os.Parcelable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import com.navi.base.utils.isNotNullAndNotEmpty
import com.navi.base.utils.orFalse
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.navi.design.textview.model.NaviSpan
import com.navi.design.textview.model.TextWithStyle
import com.navi.design.utils.*
import com.navi.naviwidgets.R
import com.navi.design.utils.dpToPx
import com.navi.design.utils.getNaviDrawable
import com.navi.design.utils.parseColorSafe
import com.navi.design.utils.setSpannableString
import com.navi.naviwidgets.adapters.TopProductAdapter
import com.navi.naviwidgets.callbacks.WidgetCallback
import com.navi.naviwidgets.databinding.LayoutTopProductBinding
import com.navi.naviwidgets.databinding.TopProductItemBinding
import com.navi.naviwidgets.extensions.showWhenDataIsAvailable
import com.navi.naviwidgets.models.response.TopProductItem
import com.navi.naviwidgets.models.response.TopProductWidget
import com.navi.naviwidgets.utils.NaviWidgetIconUtils
class TopProductLayout @JvmOverloads constructor(
context: Context,
@@ -45,6 +39,7 @@ class TopProductLayout @JvmOverloads constructor(
private lateinit var widgetCallback: WidgetCallback
private var widgetPosition: Int = Int.MAX_VALUE
private var lifecycle: Lifecycle? = null
var linearLayoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
fun update(
widgetData: TopProductWidget,
@@ -89,155 +84,22 @@ class TopProductLayout @JvmOverloads constructor(
}
}
private fun restoreInstanceState(state: Parcelable?) {
if (state != null) {
linearLayoutManager.onRestoreInstanceState(state)
} else {
linearLayoutManager.scrollToPosition(0)
}
}
private fun bindItems(widgets: List<TopProductItem>?) {
val inflater = LayoutInflater.from(context)
widgets?.forEachIndexed { position, widget ->
val view =
inflater.inflate(R.layout.top_product_item, binding.itemContainer, false)
val itemBinding: TopProductItemBinding = DataBindingUtil.bind(view)!!
itemBinding.productLabel.apply {
background = getNaviDrawable(
radii = CornerRadius(
0f,
0f,
dpToPx(4),
dpToPx(4)
),
backgroundColor = widget.label?.bgColor.parseColorSafe()
)
setSpannableString(widget.label?.title)
restoreInstanceState(widgetData.widgetData?.content?.scrollState)
binding.itemListRv.apply {
layoutManager = linearLayoutManager
adapter = TopProductAdapter(widgets.orEmpty(), widgetCallback)
setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
widgetData.widgetData?.content?.scrollState = linearLayoutManager.onSaveInstanceState()
}
itemBinding.productTitle.setSpannableString(widget.title)
itemBinding.productSubtitle.setSpannableString(widget.subTitle)
itemBinding.productDescription.setSpannableString(widget.description)
widget.imageDetails?.let {
it.height?.let {
itemBinding.productImage.layoutParams.height = dpToPxInInt(it)
}
it.width?.let {
itemBinding.productImage.layoutParams.width = dpToPxInInt(it)
}
widget.imageMargin?.let { margin ->
val lp = itemBinding.productImage.layoutParams as? MarginLayoutParams
lp?.bottomMargin = dpToPxInInt(margin.bottomDp.toInt())
lp?.marginEnd = dpToPxInInt(margin.endDp.toInt())
itemBinding.productImage.layoutParams = lp
}
itemBinding.productImage.requestLayout()
it.iconCode?.let {
NaviWidgetIconUtils.updateIcon(it, itemBinding.productImage)
}
} ?: kotlin.run {
widget.iconCode?.let {
NaviWidgetIconUtils.updateIcon(it, itemBinding.productImage)
}
}
widget.rightIconDetails?.let {
it.height?.let { height ->
itemBinding.rightArrowIv.layoutParams.height = dpToPxInInt(height)
}
it.width?.let { width ->
itemBinding.rightArrowIv.layoutParams.width = dpToPxInInt(width)
}
itemBinding.rightArrowIv.requestLayout()
it.iconCode?.let { iconCode ->
NaviWidgetIconUtils.updateIcon(iconCode, itemBinding.rightArrowIv)
}
} ?: kotlin.run {
widget.rightArrowIconCode?.let {
NaviWidgetIconUtils.updateIcon(it, itemBinding.rightArrowIv)
}
}
widget.subtitleTopMargin?.let {
val lp = itemBinding.productSubtitle.layoutParams as? ConstraintLayout.LayoutParams
lp?.topMargin = dpToPx(it).toInt()
itemBinding.productSubtitle.layoutParams = lp
}
widget.footerTitle?.let {
itemBinding.productStrategyTv.setSpannableString(widget.footerTitle)
widget.footerIconCode?.let {
NaviWidgetIconUtils.updateIcon(it, itemBinding.productStrategyIv)
}
val footerBg = getNaviDrawable(
radii = CornerRadius(
leftBottom = dpToPx(4),
rightBottom = dpToPx(4)
),
strokeColor = if (widget.requiresStroke.orFalse()) ContextCompat.getColor(
context,
DesignR.color.border_grey_color
) else Color.TRANSPARENT,
strokeWidth = dpToPx(1).toInt(),
backgroundColor = if(widget.footerBgColor.isNotNullAndNotEmpty())
widget.footerBgColor.parseColorSafe() else Color.TRANSPARENT
)
val footerDrawable = LayerDrawable(arrayOf(footerBg))
footerDrawable.setLayerInset(0, 0, -dpToPxInInt(1), 0, 0)
itemBinding.productStrategyContainer.background = footerDrawable
} ?: kotlin.run {
itemBinding.productStrategyContainer.visibility = View.GONE
itemBinding.divider.visibility = View.GONE
}
val colors = mutableListOf<Int>()
widget.gradient?.let {
it.startGradientColor?.let { colors.add(Color.parseColor(it)) }
it.middleGradientColor?.let { colors.add(Color.parseColor(it)) }
it.endGradientColor?.let { colors.add(Color.parseColor(it)) }
}
itemBinding.productCard.background = getNaviDrawable(
cornerRadius = dpToPx(4).toInt(),
strokeColor = if (widget.requiresStroke.orFalse()) ContextCompat.getColor(
context,
DesignR.color.border_grey_color
) else Color.TRANSPARENT,
strokeWidth = dpToPx(1).toInt(),
backgroundColor = if(widget.bgColor.isNotNullAndNotEmpty())
widget.bgColor.parseColorSafe() else Color.TRANSPARENT,
gradientColors = widget.gradient?.let { colors.toIntArray() },
gradientOrientation = GradientOrientation.valueOf(
widget.gradient?.orientation ?: GradientOrientation.LEFT_RIGHT.name
)
)
if (widgets.size == 1) {
val layoutParams = LinearLayout.LayoutParams(
deviceWidth() - resources.getDimension(DesignR.dimen.dp_32).toInt(),
if(widget.cardHeight != null) dpToPx(widget.cardHeight).toInt()
else resources.getDimension(DesignR.dimen.dp_148).toInt()
)
layoutParams.rightMargin = dpToPx(16).toInt()
layoutParams.topMargin = dpToPx(10).toInt()
layoutParams.leftMargin = dpToPx(16).toInt()
layoutParams.bottomMargin = dpToPx(10).toInt()
itemBinding.widgetContainer.layoutParams = layoutParams
} else if (position == widgets.size - 1) {
val layoutParams = LinearLayout.LayoutParams(
(deviceWidth() * 0.77).toInt(),
if(widget.cardHeight != null) dpToPx(widget.cardHeight).toInt()
else resources.getDimension(DesignR.dimen.dp_148).toInt()
)
layoutParams.rightMargin = dpToPx(16).toInt()
layoutParams.topMargin = dpToPx(10).toInt()
layoutParams.leftMargin = dpToPx(16).toInt()
layoutParams.bottomMargin = dpToPx(10).toInt()
itemBinding.widgetContainer.layoutParams = layoutParams
} else {
val layoutParams = LinearLayout.LayoutParams(
(deviceWidth() * 0.77).toInt(),
if (widget.cardHeight != null) dpToPxInInt(widget.cardHeight)
else resources.getDimension(DesignR.dimen.dp_148).toInt()
)
layoutParams.topMargin = dpToPx(10).toInt()
layoutParams.leftMargin = dpToPx(16).toInt()
layoutParams.bottomMargin = dpToPx(10).toInt()
itemBinding.widgetContainer.layoutParams = layoutParams
}
itemBinding.root.setOnClickListener {
widget.actionData?.let { actionData ->
widgetCallback.widgetAnalytics(actionData.metaData?.clickedData)
widgetCallback.onClick(actionData)
}
}
binding.itemContainer.addView(view)
}
}
}

View File

@@ -66,20 +66,12 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/title_container" />
<HorizontalScrollView
android:id="@+id/scroll_view"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/item_list_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_6"
android:scrollbars="none"
app:layout_constraintTop_toBottomOf="@+id/title_container">
<LinearLayout
android:id="@+id/item_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal" />
</HorizontalScrollView>
app:layout_constraintTop_toBottomOf="@id/title_container"/>
<com.navi.design.textview.NaviTextView
android:id="@+id/cta_tv"
@@ -91,7 +83,7 @@
android:paddingHorizontal="@dimen/dp_16"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/scroll_view"
app:layout_constraintTop_toBottomOf="@+id/list_rv"
tools:text="Explore all Navi funds" />
</com.navi.naviwidgets.widgets.TopProductLayout>