feat(cart): 優化購物車品項數量調整邏輯與介面顯示

- 新增單一品項與總數量的調整限制邏輯,達上限時按鈕切換為 disabled 狀態

- 移除數量調整按鈕的陰影跳動與裁切問題

- 新增增加、減少、刪除按鈕的水波紋點擊反饋

- 在品項金額後方新增「庫存xx件」資訊,並統一樣式

- 購物車達數量上限時,顯示「已達數量上限」警告文字
This commit is contained in:
TaiwanStar Developer 2026-06-12 13:35:39 +08:00
parent e331e399bc
commit 0a19e965a8
8 changed files with 196 additions and 62 deletions

View File

@ -1,6 +1,8 @@
package com.unibuy.v2.adapter
import android.graphics.PorterDuff
import android.widget.ImageView
import androidx.core.content.ContextCompat
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.unibuy.smartdevice.R
@ -12,6 +14,11 @@ import com.unibuy.v2.UnibuyHelper
/**
* 購物車品項清單 Adapter Luzui v2 移植
* 依賴 BaseRecyclerViewAdapterHelper BaseQuickAdapter
*
* Disabled 按鈕規則
* - buttonMinus cartSelectedCount == 1 disabled最低 1 不允許繼續減少
* - buttonPlus cartSelectedCount >= slot.count庫存上限或全車總件數 >= 8 disabled
* Disabled 外觀背景換 bg_quantity_button_disabled圖示染灰#ADADADclickable = false
*/
class CartOperationAdapter(layoutResId: Int, data: MutableList<SlotStructure>) :
BaseQuickAdapter<SlotStructure, BaseViewHolder>(layoutResId, data) {
@ -22,8 +29,9 @@ class CartOperationAdapter(layoutResId: Int, data: MutableList<SlotStructure>) :
override fun convert(holder: BaseViewHolder, item: SlotStructure) {
val result = UnibuyHelper.getCartViewModel().createResult(item)
val ctx = holder.itemView.context
// 多國語商品名稱
// ── 多國語商品名稱 ──────────────────────────────────────────────
val lang = java.util.Locale.getDefault().language // "zh" / "en" / "ja"
val name = when {
lang.equals("en", ignoreCase = true) ->
@ -43,13 +51,58 @@ class CartOperationAdapter(layoutResId: Int, data: MutableList<SlotStructure>) :
holder.setText(R.id.textProductName, name)
.setText(R.id.totalPriceTextView, "NT$ ${result.currentItemPrice}")
.setText(R.id.textStock, String.format("庫存%02d件", item.count))
.setText(R.id.textQuantity, "${result.currentItemCount}")
.setText(R.id.textPrice, "NT$ ${item.product.realPrice}")
// ── 商品圖片 ────────────────────────────────────────────────────
val imageProductPicture = holder.getView<ImageView>(R.id.image_product)
try {
ImageGlide(imageProductPicture.context).fileload(item.product.productImg, imageProductPicture)
} catch (e: LogsEmptyException) {
e.printStackTrace()
}
// ── 按鈕 Disabled 狀態判斷 ──────────────────────────────────────
val buttonMinus = holder.getView<ImageView>(R.id.buttonMinus)
val buttonPlus = holder.getView<ImageView>(R.id.buttonPlus)
val isAtMinCount = item.cartSelectedCount <= 1
val isAtStockLimit = item.cartSelectedCount >= item.count
val isCartFull = UnibuyHelper.getCartViewModel().hasMinimumCartItems()
val isPlusDisabled = isAtStockLimit || isCartFull
// ── 減少按鈕 ────────────────────────────────────────────────────
applyButtonState(buttonMinus, disabled = isAtMinCount, ctx)
// ── 增加按鈕 ────────────────────────────────────────────────────
applyButtonState(buttonPlus, disabled = isPlusDisabled, ctx)
}
/**
* 套用啟用 / 停用外觀到 ImageView 按鈕
*
* @param button 目標按鈕
* @param disabled true = 停用light_gray 背景 + 灰色圖示 + 不可點擊
* @param ctx Context取色用
*/
private fun applyButtonState(button: ImageView, disabled: Boolean, ctx: android.content.Context) {
if (disabled) {
button.setBackgroundResource(R.drawable.bg_quantity_button_disabled)
// setBackgroundResource 會重置 stateListAnimator需立刻清除以防止陰影跳動
button.stateListAnimator = null
button.setColorFilter(
ContextCompat.getColor(ctx, R.color.gray),
PorterDuff.Mode.SRC_IN
)
button.isClickable = false
} else {
button.setBackgroundResource(R.drawable.bg_quantity_outline_button)
// setBackgroundResource 會重置 stateListAnimator需立刻清除以防止陰影跳動
button.stateListAnimator = null
button.clearColorFilter()
button.isClickable = true
}
}
}

View File

@ -196,6 +196,12 @@ public class ShoppingCartFragment extends FragmentAbstract {
allCartItems = UnibuyHelper.INSTANCE.getCartViewModel().getAllCartItems();
binding.totalItemsTextView.setText(getString(R.string.total_items_format, allCartItems.getTotalCount()));
binding.totalAmountTextView.setText(getString(R.string.total_amount_format, allCartItems.getTotalPrice()));
if (UnibuyHelper.INSTANCE.getCartViewModel().hasMinimumCartItems()) {
binding.maxItemsWarningTextView.setVisibility(View.VISIBLE);
} else {
binding.maxItemsWarningTextView.setVisibility(View.GONE);
}
}
/** 清空購物車並切回 VMC 商品列表。 */

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 停用狀態背景light_gray -->
<solid android:color="@color/light_gray" />
<!-- 與啟用按鈕同款圓角 -->
<corners android:radius="8dp" />
</shape>

View File

@ -1,12 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 頁面背景色 -->
<solid android:color="@color/background" />
<!-- 黑色外框線 -->
<stroke
android:width="1.5dp"
android:color="@color/black" />
<!-- 圓角 -->
<corners android:radius="8dp" />
</shape>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/light_gray">
<item>
<shape android:shape="rectangle">
<!-- 頁面背景色 -->
<solid android:color="@color/background" />
<!-- 黑色外框線 -->
<stroke
android:width="1.5dp"
android:color="@color/black" />
<!-- 圓角 -->
<corners android:radius="8dp" />
</shape>
</item>
</ripple>

View File

@ -1,12 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 頁面背景色 -->
<solid android:color="@color/background" />
<!-- 警告色外框線 -->
<stroke
android:width="1.5dp"
android:color="@color/warning" />
<!-- 圓角 -->
<corners android:radius="8dp" />
</shape>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/light_gray">
<item>
<shape android:shape="rectangle">
<!-- 頁面背景色 -->
<solid android:color="@color/background" />
<!-- 警告色外框線 -->
<stroke
android:width="1.5dp"
android:color="@color/warning" />
<!-- 圓角 -->
<corners android:radius="8dp" />
</shape>
</item>
</ripple>

View File

@ -8,16 +8,35 @@
android:orientation="vertical">
<!-- 購物車上方商品數量 -->
<TextView
android:id="@+id/totalItemsTextView"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:paddingVertical="16dp"
android:text="@string/total_items_format"
android:textColor="@color/black"
android:textSize="23sp"
android:textStyle="bold" />
android:gravity="center_vertical">
<TextView
android:id="@+id/totalItemsTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/total_items_format"
android:textColor="@color/black"
android:textSize="23sp"
android:textStyle="bold" />
<TextView
android:id="@+id/maxItemsWarningTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/cart_max_items_warning"
android:textColor="@color/warning"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="gone" />
</LinearLayout>
<!-- 购物车商品列表 -->
<androidx.recyclerview.widget.RecyclerView

View File

@ -50,17 +50,35 @@
android:text="$ 0"
style="@style/TextAppearance.App.QuickCart.Small" />
<!-- 小記金額(移至此處) -->
<TextView
android:id="@+id/totalPriceTextView"
<!-- 庫存與小記金額 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:maxLines="1"
android:text="$ 0.0000"
style="@style/TextAppearance.App.QuickCart.Medium"
android:textColor="@color/primary" />
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/totalPriceTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="$ 0.0000"
style="@style/TextAppearance.App.QuickCart.Medium"
android:textColor="@color/primary" />
<TextView
android:id="@+id/textStock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="庫存00件"
android:layout_marginStart="16dp"
style="@style/TextAppearance.App.QuickCart.Medium"
android:textColor="@color/gray"
android:textSize="24sp"
android:textStyle="normal" />
</LinearLayout>
</LinearLayout>
@ -69,34 +87,50 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
<ImageView
android:id="@+id/buttonMinus"
android:layout_width="44dp"
android:layout_height="44dp"
android:padding="10dp"
android:background="@drawable/bg_quantity_outline_button"
android:elevation="4dp"
android:src="@drawable/remove_24" />
<TextView
android:id="@+id/textQuantity"
android:layout_width="86dp"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="1"
style="@style/TextAppearance.App.QuickCart.Medium"
android:textColor="@color/black" />
android:gravity="center_vertical"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal">
<ImageView
android:id="@+id/buttonMinus"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_margin="6dp"
android:padding="10dp"
android:background="@drawable/bg_quantity_outline_button"
android:elevation="4dp"
android:stateListAnimator="@null"
android:src="@drawable/remove_24" />
<TextView
android:id="@+id/textQuantity"
android:layout_width="86dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="1"
style="@style/TextAppearance.App.QuickCart.Medium"
android:textColor="@color/black" />
<ImageView
android:id="@+id/buttonPlus"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_margin="6dp"
android:padding="10dp"
android:background="@drawable/bg_quantity_outline_button"
android:elevation="4dp"
android:stateListAnimator="@null"
android:src="@drawable/add_24" />
</LinearLayout>
<ImageView
android:id="@+id/buttonPlus"
android:layout_width="44dp"
android:layout_height="44dp"
android:padding="10dp"
android:background="@drawable/bg_quantity_outline_button"
android:elevation="4dp"
android:src="@drawable/add_24" />
</LinearLayout>
<!-- 4. 刪除鈕(現有 Adapter / ShoppingCartFragment 沿用) -->
@ -105,9 +139,13 @@
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="32dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:layout_marginEnd="6dp"
android:padding="10dp"
android:background="@drawable/bg_waste_outline_button"
android:elevation="4dp"
android:stateListAnimator="@null"
android:src="@drawable/close_warning_24" />
</LinearLayout>

View File

@ -336,6 +336,8 @@
<string name="cart_cannot_decrease">不能減少了</string>
<string name="cart_max_items">購物車最多可購買 8 件商品</string>
<string name="cart_added">已加入購物車</string>
<string name="cart_max_items_warning">已達數量上限</string>
<string name="data_error">數據異常</string>
<string name="product_delivery_success">商品出貨成功</string>
<string name="please_go_to_cabinet_to_pick_up_goods">請至99號櫃門拿取您的商品</string>