[FEAT] General flavor 多品項購物車(Part 1)
移植 Luzui v2 Kotlin MVVM 購物車到 General flavor,接 TaiwanStar 既有
結帳鏈(BuyDialog → 付款 → DispatchDialog → MQTT 結案)、沿用 VMC 出貨。
- build:啟用 Kotlin/lifecycle/BaseRecyclerViewAdapterHelper(全域 classpath,
cart .kt 僅在 src/General 編譯,不影響 Chengwai/Cmuh 行為)
- 移植至 src/General:CartViewModel/CartOperationAdapter/CartOperationResult/
SafeCountdownUtil/UnibuyHelper/ShoppingCartFragment + layout/drawable/三語 strings
- 移除 Luzui 的 updateLocalGoods(FLOWER/DAO)、DialogXManager、HttpAPI、格子機依賴
- 結帳改 convertToBuyStructure() + SaleFlowHandler.onBuyRequested()
- 共用檔最小改動(對 Chengwai/Cmuh 零行為影響):
- SlotStructure 加 cartSelectedCount
- FontendActivity 加 openFragment()/backToVmc()、購物車浮鈕改走 onShoppingCartRequested()
- RecyclerVmcBuyListAdpter 點商品改走 flavor hook handleProductSelected()
- 三個 SaleFlowHandler 加 onShoppingCartRequested()/handleProductSelected()
(General:加入購物車;Chengwai/Cmuh:維持原行為)
三 flavor 編譯通過、General APK 打包成功。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5f480d6cb6
commit
cfc802dbb5
@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@ -111,6 +112,10 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
buildConfig true
|
||||
@ -146,4 +151,11 @@ dependencies {
|
||||
implementation 'io.netty:netty-codec-http:4.1.94.Final'
|
||||
implementation 'io.netty:netty-handler:4.1.94.Final'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
|
||||
// Kotlin / MVVM(為 General flavor 的多品項購物車)
|
||||
implementation libs.kotlin.stdlib
|
||||
implementation libs.lifecycle.viewmodel.ktx
|
||||
implementation libs.lifecycle.livedata.ktx
|
||||
implementation libs.lifecycle.runtime.ktx
|
||||
implementation libs.brvah
|
||||
}
|
||||
|
||||
@ -32,6 +32,16 @@ public class SaleFlowHandler {
|
||||
new MemberCardPickupDialog(context, handlerMain).show();
|
||||
}
|
||||
|
||||
public void onShoppingCartRequested() {
|
||||
// 晟崴版維持原行為:購物車浮鈕等同進入購買流程
|
||||
onBuyRequested();
|
||||
}
|
||||
|
||||
/** 晟崴版不消化商品點擊,交回 adapter 既有流程(員工卡取貨)。 */
|
||||
public boolean handleProductSelected(com.unibuy.smartdevice.structure.SlotStructure slot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onBarcodeReceived(String rawData) {
|
||||
// 晟崴版不處理掃碼
|
||||
}
|
||||
|
||||
@ -87,6 +87,16 @@ public class SaleFlowHandler {
|
||||
// 中國醫版沒有購物車付款,此方法不執行
|
||||
}
|
||||
|
||||
public void onShoppingCartRequested() {
|
||||
// 中國醫版維持原行為(不處理購物車浮鈕)
|
||||
onBuyRequested();
|
||||
}
|
||||
|
||||
/** 中國醫版不消化商品點擊,交回 adapter 既有流程。 */
|
||||
public boolean handleProductSelected(com.unibuy.smartdevice.structure.SlotStructure slot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onBarcodeReceived(String rawData) {
|
||||
// 若是經由其他方式傳入的 Barcode 可在此處直接處理
|
||||
try {
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
package com.unibuy.smartdevice.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import com.blankj.utilcode.util.ToastUtils;
|
||||
import com.unibuy.smartdevice.R;
|
||||
import com.unibuy.smartdevice.structure.SlotStructure;
|
||||
import com.unibuy.smartdevice.tools.HandlerMain;
|
||||
import com.unibuy.smartdevice.ui.dialog.BuyDialog;
|
||||
import com.unibuy.v2.UnibuyHelper;
|
||||
import com.unibuy.v2.ui.ShoppingCartFragment;
|
||||
|
||||
/**
|
||||
* General (通用版) 銷售流程處理器。
|
||||
* 點選商品/購物車時彈出標準付款 Dialog (BuyDialog)。
|
||||
* 結帳時彈出標準付款 Dialog (BuyDialog);購物車浮鈕則開啟多品項購物車 Fragment。
|
||||
*/
|
||||
public class SaleFlowHandler {
|
||||
|
||||
@ -14,6 +19,9 @@ public class SaleFlowHandler {
|
||||
protected final SaleFlowCallback callback;
|
||||
protected final HandlerMain handlerMain;
|
||||
|
||||
/** 多品項購物車 Fragment(懶建立,重複開啟時沿用同一實例)。 */
|
||||
private ShoppingCartFragment cartFragment;
|
||||
|
||||
public SaleFlowHandler(Context context, HandlerMain handlerMain, SaleFlowCallback callback) {
|
||||
this.context = context;
|
||||
this.handlerMain = handlerMain;
|
||||
@ -32,6 +40,27 @@ public class SaleFlowHandler {
|
||||
new BuyDialog(context, handlerMain).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 點選 VMC 商品:加入多品項購物車並停留商品列表(不做取貨口檢查,出貨時才檢查)。
|
||||
* @return true 表示已消化此次點擊(General 一律 true)。
|
||||
*/
|
||||
public boolean handleProductSelected(SlotStructure slot) {
|
||||
UnibuyHelper.INSTANCE.getCartViewModel().addToCart(slot);
|
||||
ToastUtils.showShort(R.string.cart_added);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 購物車浮鈕:開啟多品項購物車 Fragment。 */
|
||||
public void onShoppingCartRequested() {
|
||||
if (!(context instanceof FontendActivity)) {
|
||||
return;
|
||||
}
|
||||
if (cartFragment == null) {
|
||||
cartFragment = new ShoppingCartFragment(handlerMain);
|
||||
}
|
||||
((FontendActivity) context).openFragment(cartFragment);
|
||||
}
|
||||
|
||||
public void onBarcodeReceived(String rawData) {
|
||||
// 通用版不處理掃碼
|
||||
}
|
||||
|
||||
35
app/src/General/java/com/unibuy/v2/UnibuyHelper.kt
Normal file
35
app/src/General/java/com/unibuy/v2/UnibuyHelper.kt
Normal file
@ -0,0 +1,35 @@
|
||||
package com.unibuy.v2
|
||||
|
||||
import com.blankj.utilcode.util.ActivityUtils
|
||||
import com.blankj.utilcode.util.AppUtils
|
||||
import com.unibuy.smartdevice.ui.FontendActivity
|
||||
import com.unibuy.v2.vm.CartViewModel
|
||||
|
||||
/**
|
||||
* v2 購物車的橋接 helper(自 Luzui 移植並裁剪)。
|
||||
*
|
||||
* 與 Luzui 差異:
|
||||
* - getCartViewModel() 改為自持 singleton,不綁 FontendActivity 的 ViewModelProvider,
|
||||
* 避免修改 main 共用的 FontendActivity(其無 cartViewModel 欄位)。
|
||||
* - 移除 dismissAllAlertDialogs()/getSPStrNotNull():前者依賴 DialogAbstract.dialogInstances
|
||||
* (TaiwanStar 為 private static 無法存取),後者購物車未使用。
|
||||
*/
|
||||
object UnibuyHelper {
|
||||
|
||||
private val cartViewModel: CartViewModel = CartViewModel()
|
||||
|
||||
fun getCartViewModel(): CartViewModel = cartViewModel
|
||||
|
||||
fun getFontendActivity(): FontendActivity {
|
||||
if (ActivityUtils.getTopActivity() is FontendActivity) {
|
||||
return ActivityUtils.getTopActivity() as FontendActivity
|
||||
} else if (ActivityUtils.isActivityExists(AppUtils.getAppPackageName(), FontendActivity::class.java.simpleName)) {
|
||||
ActivityUtils.getActivityList().forEach { activity ->
|
||||
if (activity is FontendActivity) {
|
||||
return activity
|
||||
}
|
||||
}
|
||||
}
|
||||
return FontendActivity()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package com.unibuy.v2.adapter
|
||||
|
||||
import android.widget.ImageView
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
||||
import com.unibuy.smartdevice.R
|
||||
import com.unibuy.smartdevice.exception.LogsEmptyException
|
||||
import com.unibuy.smartdevice.structure.SlotStructure
|
||||
import com.unibuy.smartdevice.ui.tools.ImageGlide
|
||||
import com.unibuy.v2.UnibuyHelper
|
||||
|
||||
/**
|
||||
* 購物車品項清單 Adapter(自 Luzui v2 移植)。
|
||||
* 依賴 BaseRecyclerViewAdapterHelper 的 BaseQuickAdapter。
|
||||
*/
|
||||
class CartOperationAdapter(layoutResId: Int, data: MutableList<SlotStructure>) :
|
||||
BaseQuickAdapter<SlotStructure, BaseViewHolder>(layoutResId, data) {
|
||||
|
||||
init {
|
||||
addChildClickViewIds(R.id.buttonMinus, R.id.buttonPlus, R.id.buttonWaste, R.id.layout_product, R.id.image_product)
|
||||
}
|
||||
|
||||
override fun convert(holder: BaseViewHolder, item: SlotStructure) {
|
||||
val result = UnibuyHelper.getCartViewModel().createResult(item)
|
||||
|
||||
// 多國語商品名稱
|
||||
val lang = java.util.Locale.getDefault().language // "zh" / "en" / "ja"
|
||||
val name = when {
|
||||
lang.equals("en", ignoreCase = true) ->
|
||||
item.product.productNameEn
|
||||
lang.equals("ja", ignoreCase = true) ||
|
||||
lang.equals("jp", ignoreCase = true) ->
|
||||
item.product.productNameJp
|
||||
else ->
|
||||
item.product.productName
|
||||
}.let { localizedName ->
|
||||
if (localizedName.isNullOrBlank()) {
|
||||
item.product.productName
|
||||
} else {
|
||||
localizedName
|
||||
}
|
||||
}
|
||||
|
||||
holder.setText(R.id.textProductName, name)
|
||||
.setText(R.id.totalPriceTextView, "NT$ ${result.currentItemPrice}")
|
||||
.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.unibuy.v2.bean
|
||||
|
||||
// 購物車操作結果資料類
|
||||
data class CartOperationResult(
|
||||
val totalPrice: Int, // 購物車總價
|
||||
val totalCount: Int, // 購物車總數量
|
||||
val currentItemCount: Int, // 當前商品數量
|
||||
val currentItemPrice: Int // 當前商品價格
|
||||
)
|
||||
240
app/src/General/java/com/unibuy/v2/ui/ShoppingCartFragment.java
Normal file
240
app/src/General/java/com/unibuy/v2/ui/ShoppingCartFragment.java
Normal file
@ -0,0 +1,240 @@
|
||||
package com.unibuy.v2.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.blankj.utilcode.util.ClickUtils;
|
||||
import com.blankj.utilcode.util.ToastUtils;
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter;
|
||||
import com.unibuy.smartdevice.FragmentAbstract;
|
||||
import com.unibuy.smartdevice.R;
|
||||
import com.unibuy.smartdevice.databinding.FragmentShoppingCartBinding;
|
||||
import com.unibuy.smartdevice.structure.SlotStructure;
|
||||
import com.unibuy.smartdevice.tools.HandlerMain;
|
||||
import com.unibuy.smartdevice.tools.ToastHandlerMain;
|
||||
import com.unibuy.smartdevice.ui.FontendActivity;
|
||||
import com.unibuy.v2.UnibuyHelper;
|
||||
import com.unibuy.v2.adapter.CartOperationAdapter;
|
||||
import com.unibuy.v2.bean.CartOperationResult;
|
||||
import com.unibuy.v2.utils.SafeCountdownUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* General flavor 多品項購物車 Fragment(自 Luzui v2 移植並適配)。
|
||||
*
|
||||
* 與 Luzui 差異:
|
||||
* - 移除 HttpAPI / DialogXManager / FlowerFragment / 格子機相關邏輯。
|
||||
* - 「加購」回 VMC 商品列表;「結帳」改為 convertToBuyStructure() + SaleFlowHandler.onBuyRequested()
|
||||
* 交棒給 TaiwanStar 既有結帳鏈(BuyDialog → 付款 → DispatchDialog → MQTT 結案)。
|
||||
* - 移除 MyApp.setShoppingCartFragmentInstance(TaiwanStar 無此 API)。
|
||||
*/
|
||||
public class ShoppingCartFragment extends FragmentAbstract {
|
||||
private FragmentShoppingCartBinding binding;
|
||||
private CartOperationAdapter cartAdapter;
|
||||
private CartOperationResult allCartItems;
|
||||
private SafeCountdownUtil countdownUtil;
|
||||
|
||||
private static final int DEBOUNCE_DURATION = 500;
|
||||
private static final int COUNTDOWN_DURATION = 180;
|
||||
|
||||
/** 回傳當前倒數秒數(未初始化回 -1)。 */
|
||||
public int getCurrentCountdownSeconds() {
|
||||
if (countdownUtil != null) {
|
||||
return countdownUtil.getCurrentSeconds();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String setName() {
|
||||
return getString(R.string.shopping_cart_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Context setCtx() {
|
||||
return getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> setCls() {
|
||||
return getClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HandlerMain setHandlerMain() {
|
||||
return new ToastHandlerMain(getCtx(), getLogs()) {
|
||||
@Override
|
||||
protected void execute(Context context, int commandCode, String message) {
|
||||
// 購物車本身不需處理額外指令
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ShoppingCartFragment() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public ShoppingCartFragment(HandlerMain srcHandlerMain) {
|
||||
super(srcHandlerMain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentShoppingCartBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setupUI();
|
||||
}
|
||||
|
||||
private void setupUI() {
|
||||
setupCartRecyclerView();
|
||||
setupClickListeners();
|
||||
refreshTitleView();
|
||||
initCountdownUtil();
|
||||
}
|
||||
|
||||
private void initCountdownUtil() {
|
||||
if (countdownUtil == null) {
|
||||
countdownUtil = new SafeCountdownUtil(getViewLifecycleOwner().getLifecycle());
|
||||
countdownUtil.setCountdownListener(new SafeCountdownUtil.CountdownListener() {
|
||||
@Override
|
||||
public void onTick(int seconds) {
|
||||
updateCountdownText(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
if (isAdded() && getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> clearCartAndBack());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCountdownText(int seconds) {
|
||||
if (!isAdded() || getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (binding != null && binding.cancelText != null) {
|
||||
binding.cancelText.setText(getString(R.string.cancel_button_format, seconds));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleCartItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) {
|
||||
try {
|
||||
SlotStructure slot = (SlotStructure) adapter.getData().get(position);
|
||||
int viewId = view.getId();
|
||||
if (viewId == R.id.buttonMinus) {
|
||||
UnibuyHelper.INSTANCE.getCartViewModel().removeFromCart(slot);
|
||||
adapter.notifyItemChanged(position);
|
||||
} else if (viewId == R.id.buttonPlus) {
|
||||
UnibuyHelper.INSTANCE.getCartViewModel().addToCart(slot);
|
||||
adapter.notifyItemChanged(position);
|
||||
} else if (viewId == R.id.buttonWaste) {
|
||||
UnibuyHelper.INSTANCE.getCartViewModel().removeItemFromCart(slot);
|
||||
adapter.remove(position);
|
||||
// 購物車清空後返回 VMC 商品列表
|
||||
if (UnibuyHelper.INSTANCE.getCartViewModel().getCartItemsSnapshot().isEmpty()) {
|
||||
backToVmc();
|
||||
}
|
||||
}
|
||||
// layout_product / image_product 的商品詳情 popup 屬 Luzui DialogXManager,General 不採用
|
||||
refreshTitleView();
|
||||
} catch (Exception e) {
|
||||
ToastUtils.showLong(getString(R.string.data_error));
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupClickListeners() {
|
||||
// 加購商品:回到 VMC 商品列表繼續挑選
|
||||
ClickUtils.applySingleDebouncing(binding.addMoreItemsButton, DEBOUNCE_DURATION, v -> backToVmc());
|
||||
|
||||
// 前往結帳:寫入 buyList 後交棒給既有結帳鏈
|
||||
ClickUtils.applySingleDebouncing(binding.confirmButton, DEBOUNCE_DURATION, v -> {
|
||||
if (UnibuyHelper.INSTANCE.getCartViewModel().getCartItemsSnapshot().isEmpty()) {
|
||||
ToastUtils.showLong(getString(R.string.cart_cannot_decrease));
|
||||
return;
|
||||
}
|
||||
UnibuyHelper.INSTANCE.getCartViewModel().convertToBuyStructure();
|
||||
FontendActivity activity = getFontendActivity();
|
||||
if (activity != null && activity.getSaleFlowHandler() != null) {
|
||||
activity.getSaleFlowHandler().onBuyRequested();
|
||||
}
|
||||
});
|
||||
|
||||
// 取消:清空購物車並返回 VMC
|
||||
ClickUtils.applySingleDebouncing(binding.cancelButton, DEBOUNCE_DURATION, v -> clearCartAndBack());
|
||||
}
|
||||
|
||||
private void setupCartRecyclerView() {
|
||||
List<SlotStructure> cartItems = UnibuyHelper.INSTANCE.getCartViewModel().getCartItemsSnapshot();
|
||||
cartAdapter = new CartOperationAdapter(R.layout.recycler_cart_operation, cartItems);
|
||||
cartAdapter.setOnItemChildClickListener(this::handleCartItemClick);
|
||||
binding.shoppingCartRecyclerView.setAdapter(cartAdapter);
|
||||
}
|
||||
|
||||
private void refreshTitleView() {
|
||||
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()));
|
||||
}
|
||||
|
||||
/** 清空購物車並切回 VMC 商品列表。 */
|
||||
private void clearCartAndBack() {
|
||||
UnibuyHelper.INSTANCE.getCartViewModel().clearCart();
|
||||
backToVmc();
|
||||
}
|
||||
|
||||
private void backToVmc() {
|
||||
FontendActivity activity = getFontendActivity();
|
||||
if (activity != null) {
|
||||
activity.backToVmc();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FontendActivity getFontendActivity() {
|
||||
if (getActivity() instanceof FontendActivity) {
|
||||
return (FontendActivity) getActivity();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startOnSwitchFragment() {
|
||||
setupUI();
|
||||
countdownUtil.startOrReset(COUNTDOWN_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopOnSwitchFragment() {
|
||||
if (countdownUtil != null) {
|
||||
countdownUtil.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void countdownReset() {
|
||||
countdownUtil.startOrReset(COUNTDOWN_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
package com.unibuy.v2.utils
|
||||
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
/**
|
||||
* 與 Fragment 生命週期綁定的安全倒數計時器(自 Luzui v2 移植)。
|
||||
*/
|
||||
class SafeCountdownUtil(private val lifecycle: Lifecycle) : DefaultLifecycleObserver {
|
||||
private var timer: Timer? = null
|
||||
private var currentTime = 0
|
||||
private var listener: CountdownListener? = null
|
||||
private var lastCallTime: Long = 0
|
||||
|
||||
fun getCurrentSeconds(): Int {
|
||||
return currentTime
|
||||
}
|
||||
|
||||
init {
|
||||
lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
interface CountdownListener {
|
||||
fun onTick(seconds: Int)
|
||||
fun onFinish()
|
||||
}
|
||||
|
||||
fun setCountdownListener(listener: CountdownListener) {
|
||||
this.listener = listener
|
||||
}
|
||||
|
||||
// 安全啟動/重置方法(帶防抖)
|
||||
fun startOrReset(seconds: Int) {
|
||||
synchronized(this) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastCallTime < 200) {
|
||||
return
|
||||
}
|
||||
lastCallTime = now
|
||||
|
||||
timer?.cancel()
|
||||
currentTime = seconds
|
||||
listener?.onTick(currentTime)
|
||||
|
||||
timer = Timer().apply {
|
||||
schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
if (currentTime > 0) {
|
||||
currentTime--
|
||||
listener?.onTick(currentTime)
|
||||
} else {
|
||||
stop()
|
||||
listener?.onFinish()
|
||||
}
|
||||
}
|
||||
}, 1000, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
synchronized(this) {
|
||||
timer?.cancel()
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
stop()
|
||||
lifecycle.removeObserver(this)
|
||||
listener = null
|
||||
}
|
||||
}
|
||||
148
app/src/General/java/com/unibuy/v2/vm/CartViewModel.kt
Normal file
148
app/src/General/java/com/unibuy/v2/vm/CartViewModel.kt
Normal file
@ -0,0 +1,148 @@
|
||||
package com.unibuy.v2.vm
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.blankj.utilcode.util.ToastUtils
|
||||
import com.unibuy.smartdevice.MyApp
|
||||
import com.unibuy.smartdevice.R
|
||||
import com.unibuy.smartdevice.structure.BuyStructure
|
||||
import com.unibuy.smartdevice.structure.SlotStructure
|
||||
import com.unibuy.v2.UnibuyHelper
|
||||
import com.unibuy.v2.bean.CartOperationResult
|
||||
|
||||
/**
|
||||
* 多品項購物車 ViewModel(自 Luzui v2 移植)。
|
||||
*
|
||||
* 與 Luzui 版差異:
|
||||
* - 移除 updateLocalGoods():Luzui 用 SlotField.FLOWER + SlotsDao 在出貨後扣本地庫存,
|
||||
* TaiwanStar 為 VMC 貨道機、扣庫存由既有 DispatchDialog / MQTT 結案負責,購物車不重複處理。
|
||||
* - 寫死中文改用 R.string.cart_max_items。
|
||||
*/
|
||||
class CartViewModel : ViewModel() {
|
||||
private val _cartItems = MutableLiveData<List<SlotStructure>>(emptyList())
|
||||
val cartItems: LiveData<List<SlotStructure>> = _cartItems
|
||||
val freeGoods: MutableList<BuyStructure> = mutableListOf()
|
||||
|
||||
/** 把購物車內容寫入 MyApp.buyList,交棒給既有結帳鏈(BuyDialog → 付款 → DispatchDialog → MQTT)。 */
|
||||
fun convertToBuyStructure() {
|
||||
MyApp.getInstance().apply {
|
||||
buyList.clear()
|
||||
// 1. 免費商品(來店禮;General 不做時恆空)
|
||||
buyList += freeGoods
|
||||
// 2. 購物車商品
|
||||
buyList += UnibuyHelper.getCartViewModel()
|
||||
.getCartItemsSnapshot()
|
||||
.map { cartItem ->
|
||||
BuyStructure(cartItem.field, cartItem.slot, cartItem.product, cartItem.cartSelectedCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 購物車最多 8 件 */
|
||||
fun hasMinimumCartItems(): Boolean {
|
||||
return getAllCartItems().totalCount >= 8
|
||||
}
|
||||
|
||||
fun getCartItemsSnapshot(): List<SlotStructure> = _cartItems.value ?: emptyList()
|
||||
|
||||
// 添加商品到購物車(以 field + slot + productID 唯一識別)
|
||||
fun addToCart(slot: SlotStructure): CartOperationResult {
|
||||
val currentItems = _cartItems.value?.toMutableList() ?: mutableListOf()
|
||||
val item = currentItems.find {
|
||||
it.field == slot.field &&
|
||||
it.slot == slot.slot &&
|
||||
it.product?.productID == slot.product?.productID
|
||||
}
|
||||
|
||||
return if (item == null) {
|
||||
if (hasMinimumCartItems()) {
|
||||
ToastUtils.showLong(R.string.cart_max_items)
|
||||
return createResult(null)
|
||||
}
|
||||
slot.cartSelectedCount = 1
|
||||
val newList = currentItems.toMutableList().apply { add(slot) }
|
||||
_cartItems.value = newList
|
||||
createResult(slot)
|
||||
} else if (item.cartSelectedCount >= item.count) {
|
||||
ToastUtils.showLong(R.string.cart_out_of_stock)
|
||||
createResult(item)
|
||||
} else {
|
||||
if (UnibuyHelper.getCartViewModel().hasMinimumCartItems()) {
|
||||
ToastUtils.showLong(R.string.cart_max_items)
|
||||
createResult(item)
|
||||
} else {
|
||||
item.cartSelectedCount++
|
||||
_cartItems.value = currentItems
|
||||
createResult(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 從購物車減少一件
|
||||
fun removeFromCart(slot: SlotStructure): CartOperationResult {
|
||||
val currentItems = _cartItems.value?.toMutableList() ?: mutableListOf()
|
||||
val item = currentItems.find {
|
||||
it.field == slot.field &&
|
||||
it.slot == slot.slot &&
|
||||
it.product?.productID == slot.product?.productID
|
||||
} ?: return CartOperationResult(0, 0, 0, 0)
|
||||
|
||||
return if (item.cartSelectedCount > 1) {
|
||||
item.cartSelectedCount--
|
||||
_cartItems.value = currentItems
|
||||
createResult(item)
|
||||
} else {
|
||||
ToastUtils.showLong(R.string.cart_cannot_decrease)
|
||||
createResult(item)
|
||||
}
|
||||
}
|
||||
|
||||
// 清空購物車
|
||||
fun clearCart(): CartOperationResult {
|
||||
val result = createResult(null)
|
||||
_cartItems.value = emptyList()
|
||||
return result.copy(
|
||||
totalPrice = 0,
|
||||
totalCount = 0,
|
||||
currentItemCount = 0,
|
||||
currentItemPrice = 0
|
||||
)
|
||||
}
|
||||
|
||||
fun createResult(item: SlotStructure?): CartOperationResult {
|
||||
val currentCount = item?.cartSelectedCount ?: 0
|
||||
val currentPrice = (item?.product?.realPrice ?: 0) * currentCount
|
||||
val currentItems = _cartItems.value ?: emptyList()
|
||||
|
||||
return CartOperationResult(
|
||||
totalPrice = currentItems.sumOf { it.cartSelectedCount * (it.product?.realPrice ?: 0) },
|
||||
totalCount = currentItems.sumOf { it.cartSelectedCount },
|
||||
currentItemCount = currentCount,
|
||||
currentItemPrice = currentPrice
|
||||
)
|
||||
}
|
||||
|
||||
// 移除整筆商品
|
||||
fun removeItemFromCart(slot: SlotStructure): CartOperationResult {
|
||||
val currentItems = _cartItems.value?.toMutableList() ?: mutableListOf()
|
||||
val productIdToRemove = slot.product?.productID
|
||||
if (productIdToRemove != null) {
|
||||
currentItems.removeAll { item ->
|
||||
item.product?.productID == productIdToRemove
|
||||
}
|
||||
}
|
||||
_cartItems.value = currentItems
|
||||
return getAllCartItems()
|
||||
}
|
||||
|
||||
fun getAllCartItems(): CartOperationResult {
|
||||
val currentItems = _cartItems.value ?: emptyList()
|
||||
return CartOperationResult(
|
||||
totalPrice = currentItems.sumOf { it.cartSelectedCount * (it.product?.realPrice ?: 0) },
|
||||
totalCount = currentItems.sumOf { it.cartSelectedCount },
|
||||
currentItemCount = 0,
|
||||
currentItemPrice = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
BIN
app/src/General/res/drawable/ic_minus.png
Normal file
BIN
app/src/General/res/drawable/ic_minus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/General/res/drawable/ic_plus.png
Normal file
BIN
app/src/General/res/drawable/ic_plus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/General/res/drawable/ic_waste.png
Normal file
BIN
app/src/General/res/drawable/ic_waste.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 732 B |
139
app/src/General/res/layout/fragment_shopping_cart.xml
Normal file
139
app/src/General/res/layout/fragment_shopping_cart.xml
Normal file
@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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"
|
||||
android:background="@color/white"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- 购物车商品列表 -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/shoppingCartRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="48dp"
|
||||
android:layout_weight="1"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#B6B6B6" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/summaryContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="40dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="60dp"
|
||||
android:paddingVertical="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/totalItemsTextView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|end"
|
||||
android:text="@string/total_items_format"
|
||||
android:textColor="#767676"
|
||||
android:textSize="46sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/totalAmountTextView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center|end"
|
||||
android:text="@string/total_amount_format"
|
||||
android:textColor="@color/red"
|
||||
android:textSize="52sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/addMoreItemsButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="#D1D1D1"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="20dp"
|
||||
android:gravity="center"
|
||||
android:padding="4dp"
|
||||
android:text="@string/add_more_items"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="46sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/confirmButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="#0060E2"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="20dp"
|
||||
android:gravity="center"
|
||||
android:padding="4dp"
|
||||
android:text="@string/go_to_pay"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="46sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/cancelButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="#000000"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cancelText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="20dp"
|
||||
android:gravity="center"
|
||||
android:padding="4dp"
|
||||
android:text="@string/cancel"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="46sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
103
app/src/General/res/layout/recycler_cart_operation.xml
Normal file
103
app/src/General/res/layout/recycler_cart_operation.xml
Normal file
@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- 商品信息布局 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_product"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_product"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="22"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="32dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textProductName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:text="商品"
|
||||
android:textColor="#000000"
|
||||
android:textSize="34sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textPrice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$ 0"
|
||||
android:textColor="#1A72DD"
|
||||
android:textSize="28sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="11"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/buttonMinus"
|
||||
android:layout_width="51dp"
|
||||
android:layout_height="51dp"
|
||||
android:src="@drawable/ic_minus" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textQuantity"
|
||||
android:layout_width="86dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="1"
|
||||
android:textColor="#1A72DD"
|
||||
android:textSize="42sp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/buttonPlus"
|
||||
android:layout_width="51dp"
|
||||
android:layout_height="51dp"
|
||||
android:src="@drawable/ic_plus" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/buttonWaste"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:src="@drawable/ic_waste" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/totalPriceTextView"
|
||||
android:layout_width="180dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="end"
|
||||
android:maxLines="1"
|
||||
android:text="$ 0.0000"
|
||||
android:textColor="#000000"
|
||||
android:textSize="32sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#939393" />
|
||||
|
||||
</LinearLayout>
|
||||
16
app/src/General/res/values-en/strings.xml
Normal file
16
app/src/General/res/values-en/strings.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- General flavor multi-item shopping cart -->
|
||||
<string name="shopping_cart_title">Shopping Cart</string>
|
||||
<string name="add_more_items">Add More Items</string>
|
||||
<string name="go_to_pay">Proceed to Checkout</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="cancel_button_format">Cancel (%d sec)</string>
|
||||
<string name="total_items_format">Items: (%d)</string>
|
||||
<string name="total_amount_format">NT$ %d</string>
|
||||
<string name="cart_out_of_stock">Out of Stock</string>
|
||||
<string name="cart_cannot_decrease">Cannot Decrease Further</string>
|
||||
<string name="cart_max_items">Up to 8 items per cart</string>
|
||||
<string name="cart_added">Added to cart</string>
|
||||
<string name="data_error">Data Error</string>
|
||||
</resources>
|
||||
16
app/src/General/res/values-ja/strings.xml
Normal file
16
app/src/General/res/values-ja/strings.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- General flavor 多品目ショッピングカート -->
|
||||
<string name="shopping_cart_title">ショッピングカート</string>
|
||||
<string name="add_more_items">商品を追加</string>
|
||||
<string name="go_to_pay">お会計へ進む</string>
|
||||
<string name="cancel">キャンセル</string>
|
||||
<string name="cancel_button_format">キャンセル(%d秒)</string>
|
||||
<string name="total_items_format">商品数:(%d)</string>
|
||||
<string name="total_amount_format">NT$ %d</string>
|
||||
<string name="cart_out_of_stock">在庫切れ</string>
|
||||
<string name="cart_cannot_decrease">これ以上減らせません</string>
|
||||
<string name="cart_max_items">カートは最大8点まで購入できます</string>
|
||||
<string name="cart_added">カートに追加しました</string>
|
||||
<string name="data_error">データ異常</string>
|
||||
</resources>
|
||||
16
app/src/General/res/values/strings.xml
Normal file
16
app/src/General/res/values/strings.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- General flavor 多品項購物車 -->
|
||||
<string name="shopping_cart_title">購物車</string>
|
||||
<string name="add_more_items">加購商品</string>
|
||||
<string name="go_to_pay">前往結帳</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="cancel_button_format">取消(%d秒)</string>
|
||||
<string name="total_items_format">商品數量:(%d)</string>
|
||||
<string name="total_amount_format">NT$ %d</string>
|
||||
<string name="cart_out_of_stock">超出庫存</string>
|
||||
<string name="cart_cannot_decrease">不能減少了</string>
|
||||
<string name="cart_max_items">購物車最多可購買 8 件商品</string>
|
||||
<string name="cart_added">已加入購物車</string>
|
||||
<string name="data_error">數據異常</string>
|
||||
</resources>
|
||||
@ -10,6 +10,7 @@ public class SlotStructure {
|
||||
private boolean lock;
|
||||
private int secord;
|
||||
private JSONObject otherData;
|
||||
public int cartSelectedCount;// 本地欄位:購物車已選數量(General flavor 多品項購物車使用)
|
||||
|
||||
/*
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
|
||||
@ -716,8 +716,14 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
binding.floatButtonPickupCode.setVisibility(View.GONE);
|
||||
|
||||
binding.floatButtonShoppingCart.setOnClickListener(view -> {
|
||||
getHandlerMain().start(getClass().getSimpleName(), Option.BUY_DIALOG_OPEN.getOption(),
|
||||
getString(R.string.detect_shopping_cart));
|
||||
// 改由 flavor 的 SaleFlowHandler 決定:General 開多品項購物車 Fragment,
|
||||
// 其他 flavor 維持原行為(onShoppingCartRequested 預設轉呼叫 onBuyRequested)。
|
||||
if (saleFlowHandler != null) {
|
||||
saleFlowHandler.onShoppingCartRequested();
|
||||
} else {
|
||||
getHandlerMain().start(getClass().getSimpleName(), Option.BUY_DIALOG_OPEN.getOption(),
|
||||
getString(R.string.detect_shopping_cart));
|
||||
}
|
||||
});
|
||||
|
||||
if (MyApp.getInstance().getDevSet().isShoppingCar()) {
|
||||
@ -1135,6 +1141,28 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 將指定 Fragment 加入容器(若尚未加入)並切換顯示。
|
||||
* 供 flavor 端注入專屬 Fragment(如 General 多品項購物車)使用,本身不依賴任何 flavor 類別。
|
||||
*/
|
||||
public void openFragment(Fragment to) {
|
||||
if (to == null) {
|
||||
return;
|
||||
}
|
||||
if (!to.isAdded()) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(binding.fragmentContainer.getId(), to)
|
||||
.hide(to)
|
||||
.commitNow();
|
||||
}
|
||||
switchFragment(activeFragment, to);
|
||||
}
|
||||
|
||||
/** 切回 VMC 商品列表(供購物車「加購/取消/逾時」返回使用)。 */
|
||||
public void backToVmc() {
|
||||
switchFragment(activeFragment, vmcFragment);
|
||||
}
|
||||
|
||||
public void callbackOnBackPressed() {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
int backStackCount = fragmentManager.getBackStackEntryCount();
|
||||
|
||||
@ -177,6 +177,16 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter<RecyclerVmcBu
|
||||
// 查詢失敗時不阻擋,讓原流程繼續
|
||||
}
|
||||
|
||||
// flavor 路由:若該 flavor 自行消化此次點擊(如 General 多品項購物車「加入購物車」),
|
||||
// 則直接結束;否則走以下既有流程(晟崴員工卡取貨等),行為不變。
|
||||
if (fragment.getActivity() instanceof FontendActivity) {
|
||||
com.unibuy.smartdevice.ui.SaleFlowHandler handler =
|
||||
((FontendActivity) fragment.getActivity()).getSaleFlowHandler();
|
||||
if (handler != null && handler.handleProductSelected(slotProduct)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 點選時即時檢查出貨口狀態(上鎖防止重複點擊)
|
||||
isCheckingSlot = true;
|
||||
checkSlotHaveProduct(slotProduct.getSlot(), 1, () -> {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
}
|
||||
@ -17,6 +17,11 @@ media3-ui = '1.5.1'
|
||||
workRuntime = "2.10.0"
|
||||
tsnackBar = "V2.0.0"
|
||||
|
||||
# Kotlin / MVVM(為 General flavor 的多品項購物車)
|
||||
kotlin = "1.9.24"
|
||||
lifecycle = "2.6.2"
|
||||
brvah = "3.0.14"
|
||||
|
||||
[libraries]
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
@ -35,6 +40,14 @@ media3-ui = {group = "androidx.media3", name = "media3-ui", version.ref = "media
|
||||
work-runtime = { group = "androidx.work", name = "work-runtime", version.ref = "workRuntime" }
|
||||
tsnackBar = { group = "com.github.Redman1037", name = "TSnackBar", version.ref = "tsnackBar" }
|
||||
|
||||
# Kotlin / MVVM(為 General flavor 的多品項購物車)
|
||||
kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
|
||||
lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
|
||||
lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" }
|
||||
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
||||
brvah = { group = "io.github.cymchad", name = "BaseRecyclerViewAdapterHelper", version.ref = "brvah" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user