From cfc802dbb5a90d8e05e401c7c8604daea22c766c Mon Sep 17 00:00:00 2001 From: sky121113 Date: Wed, 10 Jun 2026 08:45:11 +0800 Subject: [PATCH] =?UTF-8?q?[FEAT]=20General=20flavor=20=E5=A4=9A=E5=93=81?= =?UTF-8?q?=E9=A0=85=E8=B3=BC=E7=89=A9=E8=BB=8A=EF=BC=88Part=201=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移植 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 --- app/build.gradle | 12 + .../smartdevice/ui/SaleFlowHandler.java | 10 + .../smartdevice/ui/SaleFlowHandler.java | 10 + .../smartdevice/ui/SaleFlowHandler.java | 31 ++- .../java/com/unibuy/v2/UnibuyHelper.kt | 35 +++ .../unibuy/v2/adapter/CartOperationAdapter.kt | 55 ++++ .../com/unibuy/v2/bean/CartOperationResult.kt | 9 + .../unibuy/v2/ui/ShoppingCartFragment.java | 240 ++++++++++++++++++ .../com/unibuy/v2/utils/SafeCountdownUtil.kt | 76 ++++++ .../java/com/unibuy/v2/vm/CartViewModel.kt | 148 +++++++++++ app/src/General/res/drawable/ic_minus.png | Bin 0 -> 16063 bytes app/src/General/res/drawable/ic_plus.png | Bin 0 -> 16207 bytes app/src/General/res/drawable/ic_waste.png | Bin 0 -> 732 bytes .../res/layout/fragment_shopping_cart.xml | 139 ++++++++++ .../res/layout/recycler_cart_operation.xml | 103 ++++++++ app/src/General/res/values-en/strings.xml | 16 ++ app/src/General/res/values-ja/strings.xml | 16 ++ app/src/General/res/values/strings.xml | 16 ++ .../smartdevice/structure/SlotStructure.java | 1 + .../smartdevice/ui/FontendActivity.java | 32 ++- .../ui/devs/vmc/RecyclerVmcBuyListAdpter.java | 10 + build.gradle | 1 + gradle/libs.versions.toml | 13 + 23 files changed, 970 insertions(+), 3 deletions(-) create mode 100644 app/src/General/java/com/unibuy/v2/UnibuyHelper.kt create mode 100644 app/src/General/java/com/unibuy/v2/adapter/CartOperationAdapter.kt create mode 100644 app/src/General/java/com/unibuy/v2/bean/CartOperationResult.kt create mode 100644 app/src/General/java/com/unibuy/v2/ui/ShoppingCartFragment.java create mode 100644 app/src/General/java/com/unibuy/v2/utils/SafeCountdownUtil.kt create mode 100644 app/src/General/java/com/unibuy/v2/vm/CartViewModel.kt create mode 100644 app/src/General/res/drawable/ic_minus.png create mode 100644 app/src/General/res/drawable/ic_plus.png create mode 100644 app/src/General/res/drawable/ic_waste.png create mode 100644 app/src/General/res/layout/fragment_shopping_cart.xml create mode 100644 app/src/General/res/layout/recycler_cart_operation.xml create mode 100644 app/src/General/res/values-en/strings.xml create mode 100644 app/src/General/res/values-ja/strings.xml create mode 100644 app/src/General/res/values/strings.xml diff --git a/app/build.gradle b/app/build.gradle index 166ce81..c8db626 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 } diff --git a/app/src/Chengwai/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java b/app/src/Chengwai/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java index bd597c7..b8b1bd0 100644 --- a/app/src/Chengwai/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java +++ b/app/src/Chengwai/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java @@ -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) { // 晟崴版不處理掃碼 } diff --git a/app/src/Cmuh/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java b/app/src/Cmuh/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java index 2c94e2e..d26c191 100644 --- a/app/src/Cmuh/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java +++ b/app/src/Cmuh/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java @@ -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 { diff --git a/app/src/General/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java b/app/src/General/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java index 2c325b6..ce99997 100644 --- a/app/src/General/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java +++ b/app/src/General/java/com/unibuy/smartdevice/ui/SaleFlowHandler.java @@ -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) { // 通用版不處理掃碼 } diff --git a/app/src/General/java/com/unibuy/v2/UnibuyHelper.kt b/app/src/General/java/com/unibuy/v2/UnibuyHelper.kt new file mode 100644 index 0000000..06db0f3 --- /dev/null +++ b/app/src/General/java/com/unibuy/v2/UnibuyHelper.kt @@ -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() + } +} diff --git a/app/src/General/java/com/unibuy/v2/adapter/CartOperationAdapter.kt b/app/src/General/java/com/unibuy/v2/adapter/CartOperationAdapter.kt new file mode 100644 index 0000000..48fd9d1 --- /dev/null +++ b/app/src/General/java/com/unibuy/v2/adapter/CartOperationAdapter.kt @@ -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) : + BaseQuickAdapter(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(R.id.image_product) + try { + ImageGlide(imageProductPicture.context).fileload(item.product.productImg, imageProductPicture) + } catch (e: LogsEmptyException) { + e.printStackTrace() + } + } +} diff --git a/app/src/General/java/com/unibuy/v2/bean/CartOperationResult.kt b/app/src/General/java/com/unibuy/v2/bean/CartOperationResult.kt new file mode 100644 index 0000000..e11bf9d --- /dev/null +++ b/app/src/General/java/com/unibuy/v2/bean/CartOperationResult.kt @@ -0,0 +1,9 @@ +package com.unibuy.v2.bean + +// 購物車操作結果資料類 +data class CartOperationResult( + val totalPrice: Int, // 購物車總價 + val totalCount: Int, // 購物車總數量 + val currentItemCount: Int, // 當前商品數量 + val currentItemPrice: Int // 當前商品價格 +) diff --git a/app/src/General/java/com/unibuy/v2/ui/ShoppingCartFragment.java b/app/src/General/java/com/unibuy/v2/ui/ShoppingCartFragment.java new file mode 100644 index 0000000..34df5d2 --- /dev/null +++ b/app/src/General/java/com/unibuy/v2/ui/ShoppingCartFragment.java @@ -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 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; + } +} diff --git a/app/src/General/java/com/unibuy/v2/utils/SafeCountdownUtil.kt b/app/src/General/java/com/unibuy/v2/utils/SafeCountdownUtil.kt new file mode 100644 index 0000000..3c801b9 --- /dev/null +++ b/app/src/General/java/com/unibuy/v2/utils/SafeCountdownUtil.kt @@ -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 + } +} diff --git a/app/src/General/java/com/unibuy/v2/vm/CartViewModel.kt b/app/src/General/java/com/unibuy/v2/vm/CartViewModel.kt new file mode 100644 index 0000000..75d4a0c --- /dev/null +++ b/app/src/General/java/com/unibuy/v2/vm/CartViewModel.kt @@ -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>(emptyList()) + val cartItems: LiveData> = _cartItems + val freeGoods: MutableList = 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 = _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 + ) + } +} diff --git a/app/src/General/res/drawable/ic_minus.png b/app/src/General/res/drawable/ic_minus.png new file mode 100644 index 0000000000000000000000000000000000000000..177ab98f9bbc75eb3ed5137429d447aa0a8fb929 GIT binary patch literal 16063 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 literal 0 HcmV?d00001 diff --git a/app/src/General/res/drawable/ic_plus.png b/app/src/General/res/drawable/ic_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..270d87037d50ffde6260fb8482d20b1687d08a2d GIT binary patch literal 16207 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 O0000 literal 0 HcmV?d00001 diff --git a/app/src/General/res/layout/fragment_shopping_cart.xml b/app/src/General/res/layout/fragment_shopping_cart.xml new file mode 100644 index 0000000..bd54c6a --- /dev/null +++ b/app/src/General/res/layout/fragment_shopping_cart.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/General/res/layout/recycler_cart_operation.xml b/app/src/General/res/layout/recycler_cart_operation.xml new file mode 100644 index 0000000..315bd55 --- /dev/null +++ b/app/src/General/res/layout/recycler_cart_operation.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/General/res/values-en/strings.xml b/app/src/General/res/values-en/strings.xml new file mode 100644 index 0000000..42bccfe --- /dev/null +++ b/app/src/General/res/values-en/strings.xml @@ -0,0 +1,16 @@ + + + + Shopping Cart + Add More Items + Proceed to Checkout + Cancel + Cancel (%d sec) + Items: (%d) + NT$ %d + Out of Stock + Cannot Decrease Further + Up to 8 items per cart + Added to cart + Data Error + diff --git a/app/src/General/res/values-ja/strings.xml b/app/src/General/res/values-ja/strings.xml new file mode 100644 index 0000000..66e41bf --- /dev/null +++ b/app/src/General/res/values-ja/strings.xml @@ -0,0 +1,16 @@ + + + + ショッピングカート + 商品を追加 + お会計へ進む + キャンセル + キャンセル(%d秒) + 商品数:(%d) + NT$ %d + 在庫切れ + これ以上減らせません + カートは最大8点まで購入できます + カートに追加しました + データ異常 + diff --git a/app/src/General/res/values/strings.xml b/app/src/General/res/values/strings.xml new file mode 100644 index 0000000..8071aaf --- /dev/null +++ b/app/src/General/res/values/strings.xml @@ -0,0 +1,16 @@ + + + + 購物車 + 加購商品 + 前往結帳 + 取消 + 取消(%d秒) + 商品數量:(%d) + NT$ %d + 超出庫存 + 不能減少了 + 購物車最多可購買 8 件商品 + 已加入購物車 + 數據異常 + diff --git a/app/src/main/java/com/unibuy/smartdevice/structure/SlotStructure.java b/app/src/main/java/com/unibuy/smartdevice/structure/SlotStructure.java index f471eef..7e42b13 100644 --- a/app/src/main/java/com/unibuy/smartdevice/structure/SlotStructure.java +++ b/app/src/main/java/com/unibuy/smartdevice/structure/SlotStructure.java @@ -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(); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java index 94a58bd..eef9021 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java @@ -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(); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java b/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java index 5027fb6..42be52d 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java @@ -177,6 +177,16 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter { diff --git a/build.gradle b/build.gradle index 565f8c2..3eff255 100644 --- a/build.gradle +++ b/build.gradle @@ -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 } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 234581a..c02819a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }