Product Detail Screen
Screen
Step 1: fragment_product_detail.xml
तीन हिस्सों वाला ConstraintLayout: NestedScrollView (gallery, info card, description, reviews, similar products), fixed bottom bar, और shared state view। Grey screen पर हर block का white background है, इसलिए page अलग-अलग cards जैसा पढ़ा जाता है।
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottom_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Image gallery (swipe) + dots -->
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/gallery"
android:layout_width="match_parent"
android:layout_height="320dp"
android:background="@color/surface" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/gallery_dots"
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="@color/surface"
app:tabBackground="@drawable/tab_dot"
app:tabGravity="center"
app:tabIndicatorHeight="0dp"
app:tabMaxWidth="18dp"
app:tabMinWidth="18dp"
app:tabPaddingEnd="0dp"
app:tabPaddingStart="0dp" />
<!-- Name, rating, price -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/surface"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleLarge"
android:textColor="@color/text_primary"
tools:text="Smart Watch Pro" />
<TextView
android:id="@+id/short_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@color/text_secondary"
tools:text="AMOLED, SpO2, 7-day battery" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/rating_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_rating"
android:paddingStart="8dp"
android:paddingTop="2dp"
android:paddingEnd="8dp"
android:paddingBottom="2dp"
android:textColor="@android:color/white"
android:textStyle="bold"
tools:text="4.5 ★" />
<TextView
android:id="@+id/rating_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="@color/text_secondary"
tools:text="128 ratings" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:baselineAligned="true"
android:orientation="horizontal">
<TextView
android:id="@+id/discount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textColor="@color/success"
android:textSize="20sp"
android:textStyle="bold"
tools:text="62% off" />
<TextView
android:id="@+id/mrp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textColor="@color/text_secondary"
android:textSize="16sp"
tools:text="₹4,999" />
<TextView
android:id="@+id/price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_primary"
android:textSize="24sp"
android:textStyle="bold"
tools:text="₹1,899" />
</LinearLayout>
<TextView
android:id="@+id/stock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textStyle="bold"
tools:text="In stock" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:text="@string/free_delivery_note"
android:textColor="@color/text_secondary"
android:textSize="13sp"
app:drawableStartCompat="@drawable/ic_truck"
app:drawableTint="@color/brand" />
<!-- Quantity stepper -->
<LinearLayout
android:id="@+id/qty_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/quantity"
android:textColor="@color/text_primary"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
android:id="@+id/minus"
style="@style/Widget.KartEasy.QtyButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/decrease"
android:text="−" />
<TextView
android:id="@+id/qty"
android:layout_width="44dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="@color/text_primary"
tools:text="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/plus"
style="@style/Widget.KartEasy.QtyButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/increase"
android:text="+" />
</LinearLayout>
<TextView
android:id="@+id/in_cart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="@color/brand"
android:visibility="gone"
tools:text="1 already in your cart" />
</LinearLayout>
<!-- Description -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/surface"
android:orientation="vertical"
android:padding="16dp">
<TextView
style="@style/KartEasy.CardTitle"
android:text="@string/product_details" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lineSpacingExtra="4dp"
android:maxLines="4"
android:textColor="@color/text_primary" />
<TextView
android:id="@+id/description_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="?attr/selectableItemBackground"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/read_more"
android:textColor="@color/brand"
android:textStyle="bold" />
</LinearLayout>
<!-- Reviews -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/surface"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/reviews_title"
style="@style/KartEasy.CardTitle"
tools:text="Ratings & reviews (128)" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/review_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
tools:listitem="@layout/item_review" />
<TextView
android:id="@+id/no_reviews"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/no_reviews"
android:textColor="@color/text_secondary"
android:visibility="gone" />
</LinearLayout>
<!-- Similar products -->
<TextView
android:id="@+id/similar_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingStart="16dp"
android:paddingTop="12dp"
android:paddingEnd="16dp"
android:text="@string/similar_products"
android:textAppearance="@style/TextAppearance.KartEasy.Section" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/similar_list"
style="@style/KartEasy.HorizontalRow"
android:paddingBottom="16dp"
tools:listitem="@layout/item_product" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- Fixed bottom bar: Add to cart / Buy now -->
<LinearLayout
android:id="@+id/bottom_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_bottom_bar"
android:elevation="8dp"
android:orientation="horizontal"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/add_to_cart"
style="@style/Widget.KartEasy.Button.Outlined"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_weight="1"
android:text="@string/add_to_cart"
app:icon="@drawable/ic_cart" />
<com.google.android.material.button.MaterialButton
android:id="@+id/buy_now"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_weight="1"
android:backgroundTint="@color/accent"
android:text="@string/buy_now"
android:textColor="@android:color/white" />
</LinearLayout>
<include
android:id="@+id/state"
layout="@layout/layout_state" />
</androidx.constraintlayout.widget.ConstraintLayout>Step 2: gallery और review items
Gallery एक और ViewPager2 है; images fitCenter use करती हैं ताकि पूरा product दिखे। Reviews छोटा indicator RatingBar दिखाते हैं।
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/surface"
android:contentDescription="@string/product_image"
android:scaleType="fitCenter" />package com.codingeasily.karteasy.ui.product
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.codingeasily.karteasy.databinding.ItemGalleryImageBinding
import com.codingeasily.karteasy.util.load
class GalleryAdapter(private val images: List<String>) : RecyclerView.Adapter<GalleryAdapter.Holder>() {
inner class Holder(val b: ItemGalleryImageBinding) : RecyclerView.ViewHolder(b.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
Holder(ItemGalleryImageBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun getItemCount() = images.size
override fun onBindViewHolder(holder: Holder, position: Int) = holder.b.image.load(images[position])
}<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<RatingBar
android:id="@+id/rating_bar"
style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:isIndicator="true"
android:numStars="5"
android:progressTint="@color/rating" />
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:textColor="@color/text_primary"
android:textStyle="bold"
tools:text="Riya Sharma" />
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_secondary"
android:textSize="12sp"
tools:text="2026-10-19" />
</LinearLayout>
<TextView
android:id="@+id/comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/text_primary"
tools:text="Battery easily lasts a week." />
</LinearLayout>package com.codingeasily.karteasy.ui.product
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.codingeasily.karteasy.data.model.Review
import com.codingeasily.karteasy.databinding.ItemReviewBinding
import com.codingeasily.karteasy.util.visibleIf
class ReviewAdapter(private val reviews: List<Review>) : RecyclerView.Adapter<ReviewAdapter.Holder>() {
inner class Holder(val b: ItemReviewBinding) : RecyclerView.ViewHolder(b.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
Holder(ItemReviewBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun getItemCount() = reviews.size
override fun onBindViewHolder(holder: Holder, position: Int) {
val r = reviews[position]
holder.b.name.text = r.name
holder.b.ratingBar.rating = r.rating.toFloat()
holder.b.date.text = r.date
holder.b.comment.text = r.comment
holder.b.comment.visibleIf(!r.comment.isNullOrBlank())
}
}Step 3: ProductDetailViewModel
Product id SavedStateHandle से आता है। Product state के अलावा ViewModel चुनी हुई quantity (stock और 10 तक सीमित), add करते समय दोनों buttons बंद करने वाला busy flag, और one-time events रखता है: एक message, या Buy now के बाद "cart पर जाओ"।
package com.codingeasily.karteasy.ui.product
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.codingeasily.karteasy.data.api.Result
import com.codingeasily.karteasy.data.api.UiState
import com.codingeasily.karteasy.data.model.ProductDetail
import com.codingeasily.karteasy.util.kartApp
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class ProductDetailViewModel(app: Application, args: SavedStateHandle) : AndroidViewModel(app) {
sealed class Event {
data class Message(val text: String) : Event()
object GoToCart : Event()
}
private val repo = app.kartApp.repository
val productId: Int = args.get<Int>(ProductDetailFragment.ARG_PRODUCT_ID) ?: 0
private val _state = MutableStateFlow<UiState<ProductDetail>>(UiState.Loading)
val state: StateFlow<UiState<ProductDetail>> = _state
/** Quantity chosen with the − / + buttons. */
private val _qty = MutableStateFlow(1)
val qty: StateFlow<Int> = _qty
private val _busy = MutableStateFlow(false)
val busy: StateFlow<Boolean> = _busy
private val _events = MutableSharedFlow<Event>()
val events: SharedFlow<Event> = _events
init { load() }
fun load() {
viewModelScope.launch {
if (_state.value !is UiState.Success) _state.value = UiState.Loading
_state.value = when (val r = repo.product(productId)) {
is Result.Success -> UiState.Success(r.data)
is Result.Error -> UiState.Error(r.message)
}
}
}
fun changeQty(delta: Int) {
val p = (_state.value as? UiState.Success)?.data ?: return
_qty.value = (_qty.value + delta).coerceIn(1, minOf(10, maxOf(1, p.stock)))
}
/** buyNow = true opens the cart right after adding. */
fun addToCart(buyNow: Boolean) {
if (_busy.value) return
_busy.value = true
viewModelScope.launch {
when (val r = repo.addToCart(productId, _qty.value)) {
is Result.Success -> {
_events.emit(Event.Message(r.message))
if (buyNow) _events.emit(Event.GoToCart)
_qty.value = 1
load() // refreshes "already in cart" count and stock
}
is Result.Error -> _events.emit(Event.Message(r.message))
}
_busy.value = false
}
}
fun toggleWishlist() {
viewModelScope.launch {
when (val r = repo.toggleWishlist(productId)) {
is Result.Success -> {
val cur = (_state.value as? UiState.Success)?.data
if (cur != null) _state.value = UiState.Success(cur.copy(inWishlist = r.data.inWishlist))
_events.emit(Event.Message(r.message))
}
is Result.Error -> _events.emit(Event.Message(r.message))
}
}
}
}Step 4: ProductDetailFragment
bind() हर view भरता है। Gallery adapter सिर्फ पहली बार बनता है (वरना "Add to cart" के बाद refresh photo 1 पर वापस कूद जाता)। 5 या कम बचे हों तो stock text red हो जाता है।
package com.codingeasily.karteasy.ui.product
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.codingeasily.karteasy.R
import com.codingeasily.karteasy.data.api.UiState
import com.codingeasily.karteasy.data.model.ProductDetail
import com.codingeasily.karteasy.databinding.FragmentProductDetailBinding
import com.codingeasily.karteasy.util.Format
import com.codingeasily.karteasy.util.collectWhenStarted
import com.codingeasily.karteasy.util.done
import com.codingeasily.karteasy.util.error
import com.codingeasily.karteasy.util.hide
import com.codingeasily.karteasy.util.loading
import com.codingeasily.karteasy.util.show
import com.codingeasily.karteasy.util.snack
import com.codingeasily.karteasy.util.strike
import com.codingeasily.karteasy.util.visibleIf
import com.google.android.material.tabs.TabLayoutMediator
class ProductDetailFragment : Fragment() {
companion object {
const val ARG_PRODUCT_ID = "productId"
}
private var _binding: FragmentProductDetailBinding? = null
private val binding get() = _binding!!
private val vm: ProductDetailViewModel by viewModels()
private var product: ProductDetail? = null
private var menu: Menu? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentProductDetailBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Toolbar menu: wishlist heart + share (MenuProvider replaces setHasOptionsMenu)
requireActivity().addMenuProvider(object : MenuProvider {
override fun onCreateMenu(m: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_product_detail, m)
menu = m
updateHeart()
}
override fun onMenuItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.action_wishlist -> { vm.toggleWishlist(); true }
R.id.action_share -> { share(); true }
else -> false
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
val similar = ProductAdapter(
fixedWidthDp = 150,
onClick = { findNavController().navigate(R.id.productDetailFragment, bundleOf(ARG_PRODUCT_ID to it.id)) },
onWishlist = { }
)
binding.similarList.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
binding.similarList.adapter = similar
binding.minus.setOnClickListener { vm.changeQty(-1) }
binding.plus.setOnClickListener { vm.changeQty(+1) }
binding.addToCart.setOnClickListener { vm.addToCart(buyNow = false) }
binding.buyNow.setOnClickListener { vm.addToCart(buyNow = true) }
binding.descriptionToggle.setOnClickListener {
val expanded = binding.description.maxLines == Int.MAX_VALUE
binding.description.maxLines = if (expanded) 4 else Int.MAX_VALUE
binding.descriptionToggle.setText(if (expanded) R.string.read_more else R.string.read_less)
}
collectWhenStarted(vm.state) { state ->
when (state) {
is UiState.Loading -> { binding.content.hide(); binding.bottomBar.hide(); binding.state.loading() }
is UiState.Error -> { binding.content.hide(); binding.bottomBar.hide(); binding.state.error(state.message) { vm.load() } }
is UiState.Success -> {
binding.state.done(); binding.content.show(); binding.bottomBar.show()
bind(state.data)
similar.submitList(state.data.similar)
}
}
}
collectWhenStarted(vm.qty) { binding.qty.text = it.toString() }
collectWhenStarted(vm.busy) { busy ->
binding.addToCart.isEnabled = !busy
binding.buyNow.isEnabled = !busy
}
collectWhenStarted(vm.events) { e ->
when (e) {
is ProductDetailViewModel.Event.Message -> snack(e.text)
ProductDetailViewModel.Event.GoToCart -> findNavController().navigate(R.id.cartFragment)
}
}
}
private fun bind(p: ProductDetail) {
val firstTime = product == null
product = p
(requireActivity() as AppCompatActivity).supportActionBar?.title = p.categoryName
if (firstTime) {
binding.gallery.adapter = GalleryAdapter(p.images)
TabLayoutMediator(binding.galleryDots, binding.gallery) { _, _ -> }.attach()
binding.galleryDots.visibleIf(p.images.size > 1)
}
binding.name.text = p.name
binding.shortDesc.text = p.shortDesc
binding.ratingChip.text = getString(R.string.rating_value, p.rating)
binding.ratingCount.text = getString(R.string.ratings_reviews, p.ratingCount)
binding.price.text = Format.price(p.price)
binding.mrp.strike(Format.price(p.mrp))
binding.mrp.visibleIf(p.mrp > p.price)
binding.discount.text = getString(R.string.percent_off, p.discountPct)
binding.discount.visibleIf(p.discountPct > 0)
val inStock = p.stock > 0
binding.stock.text = when {
!inStock -> getString(R.string.out_of_stock)
p.stock <= 5 -> getString(R.string.only_left, p.stock)
else -> getString(R.string.in_stock)
}
binding.stock.setTextColor(ContextCompat.getColor(requireContext(), if (p.stock in 1..5 || !inStock) R.color.sale else R.color.success))
binding.qtyRow.visibleIf(inStock)
binding.addToCart.isEnabled = inStock
binding.buyNow.isEnabled = inStock
binding.inCart.visibleIf(p.cartQty > 0)
binding.inCart.text = getString(R.string.already_in_cart, p.cartQty)
binding.description.text = p.description
binding.descriptionToggle.visibleIf((p.description?.length ?: 0) > 180)
binding.reviewsTitle.text = getString(R.string.reviews_title, p.ratingCount)
binding.reviewList.layoutManager = LinearLayoutManager(requireContext())
binding.reviewList.adapter = ReviewAdapter(p.reviews)
binding.noReviews.visibleIf(p.reviews.isEmpty())
binding.similarTitle.visibleIf(p.similar.isNotEmpty())
updateHeart()
}
private fun updateHeart() {
val item = menu?.findItem(R.id.action_wishlist) ?: return
val inWishlist = product?.inWishlist == true
item.setIcon(if (inWishlist) R.drawable.ic_heart else R.drawable.ic_heart_outline)
item.setTitle(if (inWishlist) R.string.remove_from_wishlist else R.string.add_to_wishlist)
}
private fun share() {
val p = product ?: return
val text = getString(R.string.share_product, p.name, Format.price(p.price))
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, text), null))
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
menu = null
product = null
}
}| stock | Text | Colour | Buttons |
|---|---|---|---|
| 0 | Out of stock | red | disabled, quantity छुपी |
| 1 – 5 | Hurry, only 3 left! | red | enabled |
| 6+ | In stock | green | enabled |
MenuProvider के साथ toolbar menu
Heart और share icons toolbar में हैं। addMenuProvider(…, viewLifecycleOwner, RESUMED) setHasOptionsMenu का modern replacement है: screen दिखने पर menu जुड़ता है और user के जाते ही अपने आप हट जाता है।
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_wishlist"
android:icon="@drawable/ic_heart_outline"
android:title="@string/add_to_wishlist"
app:iconTint="@color/on_brand"
app:showAsAction="always" />
<item
android:id="@+id/action_share"
android:icon="@drawable/ic_share"
android:title="@string/share"
app:iconTint="@color/on_brand"
app:showAsAction="ifRoom" />
</menu>Add to cart vs Buy now
Add to cart
- POST cart/add.php {product_id, qty}
- Snackbar "Added to cart."
- product पर ही रहें
- Cart tab का badge update
Buy now
- वही API call
- फिर Event.GoToCart
- Cart screen पर navigate
- user सीधे checkout पर
छोटी बातें जो मायने रखती हैं
- "Read more" सिर्फ 180 characters से लंबे descriptions पर दिखता है।
- "1 already in your cart" बताता है कि user पहले जोड़ चुका है।
- Share छोटा text बनाता है: "Check out Smart Watch Pro for just ₹1,899 on KartEasy!"
- Similar product tap करने पर ऊपर दूसरी detail screen खुलती है — Back पिछली पर लौटाता है।
- Toolbar category name दिखाता है, product name content में बड़ा title है।
