diff --git a/app/build.gradle b/app/build.gradle index 8106e2d..d7e3f7b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 1 -def verPatch = 79 +def verPatch = 80 diff --git a/app/src/main/java/com/unibuy/smartdevice/DialogAbstract.java b/app/src/main/java/com/unibuy/smartdevice/DialogAbstract.java index 7561c03..5eab9c8 100644 --- a/app/src/main/java/com/unibuy/smartdevice/DialogAbstract.java +++ b/app/src/main/java/com/unibuy/smartdevice/DialogAbstract.java @@ -101,7 +101,7 @@ public abstract class DialogAbstract extends AlertDialog { layoutParams.gravity = Gravity.BOTTOM; window.setAttributes(layoutParams); - getSrcHandlerMain().start(getClass().getSimpleName(), FontendActivity.Option.HIDDEN_MULTILANGUAGE_BUTTON.getOption(), "hidden button"); + getSrcHandlerMain().start(getClass().getSimpleName(), FontendActivity.Option.HIDE_SALE_OVERLAY.getOption(), "hide sale overlay"); } setCancelable(false); @@ -219,6 +219,6 @@ public abstract class DialogAbstract extends AlertDialog { public void cancel() { super.cancel(); // MyApp.getInstance().setShowDialog(false); // 移至 dismiss 統一處理 - getSrcHandlerMain().start(getClass().getSimpleName(), FontendActivity.Option.SHOW_MULTILANGUAGE_BUTTON.getOption(), "show button"); + getSrcHandlerMain().start(getClass().getSimpleName(), FontendActivity.Option.SHOW_SALE_OVERLAY.getOption(), "show sale overlay"); } } diff --git a/app/src/main/java/com/unibuy/smartdevice/structure/DevSetStructure.java b/app/src/main/java/com/unibuy/smartdevice/structure/DevSetStructure.java index 7df4b40..a9e9436 100644 --- a/app/src/main/java/com/unibuy/smartdevice/structure/DevSetStructure.java +++ b/app/src/main/java/com/unibuy/smartdevice/structure/DevSetStructure.java @@ -181,4 +181,32 @@ public class DevSetStructure { public boolean isAmbientTemp() { return isAmbientTemp; } public void setAmbientTemp(boolean ambientTemp) { isAmbientTemp = ambientTemp; } + // ── 支付方式「母 && 子」便利判斷 ────────────────────────────────────────── + // 各支付實際是否可用 = 母開關開啟 && 子項開啟。集中於此,讓 UI 顯示與入口防呆共用同一份真相, + // 避免在多處(BuyDialog 購物車/單品/例外分支、changeCheckout 入口)各自拼湊而漏判。 + // 刷卡機(母 isDevNFCPay)→ 信用卡 / 電子票證(卡片) / 手機支付 + // 掃碼(母 isDevScanPay)→ 玉山 EsunPay / TapPay + // Line 官方支付、現金:獨立大項,無母開關(自身即為總開關) + + /** flowType 1 信用卡:刷卡機開啟且信用卡子項開啟。 */ + public boolean canCreditCard() { return isDevNFCPay && isDevCreditCard; } + + /** flowType 2 電子票證(卡片支付):刷卡機開啟且卡片支付子項開啟。 */ + public boolean canCardPay() { return isDevNFCPay && isDevCardPay; } + + /** flowType 4 手機支付:刷卡機開啟且手機支付子項開啟。 */ + public boolean canMobilePay() { return isDevNFCPay && isDevMobilePay; } + + /** flowType 3 玉山 EsunPay:掃碼開啟且玉山子項開啟。 */ + public boolean canEsunPay() { return isDevScanPay && isDevEsunPay; } + + /** flowType 30 TapPay:掃碼開啟且 TapPay 子項開啟。 */ + public boolean canTapPay() { return isDevScanPay && isDevTapPay; } + + /** flowType 70 Line 官方支付:獨立大項,不從屬掃碼,只看自身開關。 */ + public boolean canLinePay() { return isDevLinePay; } + + /** flowType 9 現金:獨立大項,只看自身開關。 */ + public boolean canCash() { return isDevCash; } + } 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 009f8ca..0d16f57 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java @@ -92,8 +92,11 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl SET_NETWORK(6), RESET_SLOTNO(49), RESTART_HOST(7), - HIDDEN_MULTILANGUAGE_BUTTON(8), - SHOW_MULTILANGUAGE_BUTTON(9), + // 對話框開啟時隱藏銷售平台浮層(取貨碼/購物車浮鈕、上下系統資訊列),關閉時還原。 + // 原為僅隱藏語言鈕(HIDDEN/SHOW_MULTILANGUAGE_BUTTON),擴大為整個浮層,避免對話框 + // (貼底部、非滿版)周圍把這些邊緣元件漏出來。沿用 8/9 代碼不影響既有呼叫。 + HIDE_SALE_OVERLAY(8), + SHOW_SALE_OVERLAY(9), ; private int option; @@ -390,13 +393,13 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl case RESTART_HOST: new RestartHost(this).start(); break; - case HIDDEN_MULTILANGUAGE_BUTTON: - getLogs().info("隱藏多國語按紐"); - // hiddenMultiLanguageButton(); + case HIDE_SALE_OVERLAY: + getLogs().info("對話框開啟,隱藏銷售平台浮層"); + refreshSaleOverlay(); break; - case SHOW_MULTILANGUAGE_BUTTON: - getLogs().info("顯示多國語按紐"); - // showMultiLanguageButton(); + case SHOW_SALE_OVERLAY: + getLogs().info("對話框關閉,重新評估銷售平台浮層"); + refreshSaleOverlay(); break; } } @@ -548,7 +551,7 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl return; } - getLogs().info("⚠️ 無操作 3 分鐘,自動返回初始畫面(清空購物車)"); + getLogs().info("⚠️ 無操作 5 分鐘,自動返回初始畫面(清空購物車)"); MyApp.getInstance().getBuyList().clear(); // 🚩 逾時返回首頁時,務必清空購物車,避免機台進入永久忙碌狀態 com.unibuy.v2.UnibuyHelper.INSTANCE.getCartViewModel().clearCart(); // 一併清 v2 多品項購物車,避免取消結帳後走人留下殘單 Intent intent = new Intent(FontendActivity.this, AppEntry.initialActivity()); @@ -799,16 +802,31 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl } } - private void hiddenMultiLanguageButton() { - binding.btnswitchlanTW.setVisibility(View.GONE); - binding.btnswitchlanEn.setVisibility(View.GONE); - binding.btnswitchlanJa.setVisibility(View.GONE); - } - - private void showMultiLanguageButton() { - binding.btnswitchlanTW.setVisibility(VISIBLE); - binding.btnswitchlanEn.setVisibility(VISIBLE); - binding.btnswitchlanJa.setVisibility(VISIBLE); + /** + * 統一控制銷售平台「浮層」在對話框期間的顯示:取貨碼/購物車浮鈕、上方系統列(時間/溫度/網路)、 + * 下方系統列(版號/語言/執行緒)。對話框(貼底部、非滿版)只蓋住中間,這些邊緣元件若不隱藏會漏出來。 + * visible=false → 全部隱藏(對話框開啟時)。 + * visible=true → 還原;浮鈕依各自 B014 gating 重新判斷(不可無腦設 VISIBLE,否則會把已停用的 + * 取貨碼/購物車漏出來),系統列則直接還原。 + */ + private void applySaleOverlayVisibility(boolean visible) { + if (binding == null) return; + if (visible) { + // 取貨碼浮鈕:從屬取貨模組,PickupModule && PickupCode 才顯示。 + binding.floatButtonPickupCode.setVisibility( + (MyApp.getInstance().getDevSet().isPickupModule() + && MyApp.getInstance().getDevSet().isPickupCode()) ? VISIBLE : View.GONE); + // 購物車浮鈕:依購物車開關。 + binding.floatButtonShoppingCart.setVisibility( + MyApp.getInstance().getDevSet().isShoppingCar() ? VISIBLE : View.GONE); + binding.linearLayout5.setVisibility(VISIBLE); + binding.bottomBar.setVisibility(VISIBLE); + } else { + binding.floatButtonPickupCode.setVisibility(View.GONE); + binding.floatButtonShoppingCart.setVisibility(View.GONE); + binding.linearLayout5.setVisibility(View.GONE); + binding.bottomBar.setVisibility(View.GONE); + } } private class RestartHost extends HandlerMainScheduler { @@ -1143,9 +1161,24 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl getLogs().info("change pay start"); ((FragmentAbstract) to).startOnSwitchFragment(); } + + // 浮層連動:購物車(General 多品項)是開在 fragment_container 的 Fragment,不走 DialogAbstract, + // 故不會送 HIDE_SALE_OVERLAY。在此依目標 fragment 補上連動(activeFragment 已於上方更新)。 + refreshSaleOverlay(); } } + /** + * 重新評估銷售平台浮層(取貨碼/購物車浮鈕、上下系統列)是否該顯示。 + * 唯一真相:僅當「處於商品瀏覽頁(VMC/Electric)」且「沒有任何對話框開著」時才顯示,否則隱藏。 + * 涵蓋兩條會遮住銷售平台的路徑:對話框(DialogAbstract,貼底部非滿版)與購物車 Fragment。 + */ + private void refreshSaleOverlay() { + boolean inProductBrowsing = (activeFragment == vmcFragment || activeFragment == electricFragment); + boolean anyDialog = MyApp.getInstance().isShowDialog(); + applySaleOverlayVisibility(inProductBrowsing && !anyDialog); + } + /** * 將指定 Fragment 加入容器(若尚未加入)並切換顯示。 * 供 flavor 端注入專屬 Fragment(如 General 多品項購物車)使用,本身不依賴任何 flavor 類別。 diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java index 35a9fb8..e1e25f2 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java @@ -10,6 +10,7 @@ import com.unibuy.smartdevice.databinding.DialogBuyBinding; import com.unibuy.smartdevice.devices.SlotField; import com.unibuy.smartdevice.exception.LogsEmptyException; import com.unibuy.smartdevice.structure.BuyStructure; +import com.unibuy.smartdevice.structure.DevSetStructure; import com.unibuy.smartdevice.structure.ReportFreeGiftCodeStructure; import com.unibuy.smartdevice.structure.SlotStructure; import com.unibuy.smartdevice.tools.HandlerMainCountdown; @@ -253,135 +254,74 @@ public class BuyDialog extends DialogAbstract { } if (MyApp.getInstance().getDevSet().isShoppingCar()) { - // NFC 卡/儲值/手機(TaiwanStar 無 layoutNfcPay 容器,改逐顆控制),依 isDevNFCPay 顯示/隱藏 - int nfcVisibility = MyApp.getInstance().getDevSet().isDevNFCPay() ? View.VISIBLE : View.GONE; - binding.buttonNfcCard.setVisibility(nfcVisibility); - binding.buttonNfcRecharge.setVisibility(nfcVisibility); - binding.buttonNfcPhone.setVisibility(nfcVisibility); + // 刷卡機(母 isDevNFCPay)三子項:各自 母 && 子 判斷,母關則三者全隱藏。 + // 信用卡=NfcCard(canCreditCard)、電子票證/卡片=NfcRecharge(canCardPay)、手機=NfcPhone(canMobilePay) + binding.buttonNfcCard.setVisibility(MyApp.getInstance().getDevSet().canCreditCard() ? View.VISIBLE : View.GONE); + binding.buttonNfcRecharge.setVisibility(MyApp.getInstance().getDevSet().canCardPay() ? View.VISIBLE : View.GONE); + binding.buttonNfcPhone.setVisibility(MyApp.getInstance().getDevSet().canMobilePay() ? View.VISIBLE : View.GONE); - if (MyApp.getInstance().getDevSet().isDevEsunPay()) { - binding.buttonEsunPay.setVisibility(View.VISIBLE); - } else { - binding.buttonEsunPay.setVisibility(View.GONE); - } + // 掃碼(母 isDevScanPay)子項:玉山 / TapPay,母關則隱藏。 + binding.buttonEsunPay.setVisibility(MyApp.getInstance().getDevSet().canEsunPay() ? View.VISIBLE : View.GONE); + binding.buttonTapPay.setVisibility(MyApp.getInstance().getDevSet().canTapPay() ? View.VISIBLE : View.GONE); - if (MyApp.getInstance().getDevSet().isDevLinePay()) { - binding.buttonLinePay.setVisibility(View.VISIBLE); - } else { - binding.buttonLinePay.setVisibility(View.GONE); - } + // Line 官方支付:獨立大項,不從屬掃碼。 + binding.buttonLinePay.setVisibility(MyApp.getInstance().getDevSet().canLinePay() ? View.VISIBLE : View.GONE); - if (MyApp.getInstance().getDevSet().isDevCash()) { - binding.buttonCash.setVisibility(View.VISIBLE); - } else { - binding.buttonCash.setVisibility(View.GONE); - } - - if (MyApp.getInstance().getDevSet().isDevTapPay()) { - binding.buttonTapPay.setVisibility(View.VISIBLE); - } else { - binding.buttonTapPay.setVisibility(View.GONE); - } + // 現金:獨立大項。 + binding.buttonCash.setVisibility(MyApp.getInstance().getDevSet().canCash() ? View.VISIBLE : View.GONE); } else { BuyStructure buyData = MyApp.getInstance().getBuyList().get(0); try { SlotField slotField = SlotField.getSlotField(buyData.getField()); + // 貨道層級是否支援 NFC(VMC/ELECTRIC 不同);三個刷卡機子項共用這個貨道旗標。 + boolean slotNfc = slotField.isDevNFCPay(); if (MyApp.getInstance().getDevSet().isDevNFCPay()) { - if (slotField.isDevNFCPay()) { - getLogs().info("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); -// binding.layoutNfcPay.setVisibility(View.VISIBLE); - } else { -// binding.layoutNfcPay.setVisibility(View.GONE); - } + // 刷卡機開啟:三子項各自 (母&&子) 且貨道支援 NFC 才顯示。 + binding.buttonNfcCard.setVisibility( + (MyApp.getInstance().getDevSet().canCreditCard() && slotNfc) ? View.VISIBLE : View.GONE); + binding.buttonNfcRecharge.setVisibility( + (MyApp.getInstance().getDevSet().canCardPay() && slotNfc) ? View.VISIBLE : View.GONE); + binding.buttonNfcPhone.setVisibility( + (MyApp.getInstance().getDevSet().canMobilePay() && slotNfc) ? View.VISIBLE : View.GONE); } else { - getLogs().info("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB2"); + // 刷卡機關閉:三子項全隱藏,並啟用通行碼備援入口(仍須從屬取貨模組且通行碼開啟, + // 否則 NFC 不支援時會把已關閉的通行碼漏出來)。 binding.buttonPaymentTransactions.setVisibility(View.GONE); binding.buttonNfcCard.setVisibility(View.GONE); binding.buttonNfcPhone.setVisibility(View.GONE); binding.buttonNfcRecharge.setVisibility(View.GONE); - // 通行碼備援入口:仍須從屬「取貨模組」且通行碼開啟才顯示, - // 否則 NFC 不支援時會把已關閉的通行碼漏出來。 boolean passCodeOn = MyApp.getInstance().getDevSet().isPickupModule() && MyApp.getInstance().getDevSet().isPassCode(); binding.buttonAccessCode.setVisibility(passCodeOn ? View.VISIBLE : View.GONE); binding.buttonVerificationPickup.setVisibility(View.GONE); binding.layoutVerificationPickup.setVisibility(passCodeOn ? View.VISIBLE : View.GONE); -// binding.layoutNfcPay.setVisibility(View.GONE); } - if (MyApp.getInstance().getDevSet().isDevEsunPay()) { - if (slotField.isDevEsunpay()) { - binding.buttonEsunPay.setVisibility(View.VISIBLE); - } else { - binding.buttonEsunPay.setVisibility(View.GONE); - } - } else { - binding.buttonEsunPay.setVisibility(View.GONE); - } + // 掃碼子項:母(isDevScanPay)&&子 且貨道支援。 + binding.buttonEsunPay.setVisibility( + (MyApp.getInstance().getDevSet().canEsunPay() && slotField.isDevEsunpay()) ? View.VISIBLE : View.GONE); + binding.buttonTapPay.setVisibility( + (MyApp.getInstance().getDevSet().canTapPay() && slotField.isDevTapPay()) ? View.VISIBLE : View.GONE); - if (MyApp.getInstance().getDevSet().isDevLinePay()) { - if (slotField.isDevLinePay()) { - binding.buttonLinePay.setVisibility(View.VISIBLE); - } else { - binding.buttonLinePay.setVisibility(View.GONE); - } - } else { - binding.buttonLinePay.setVisibility(View.GONE); - } + // Line 官方支付:獨立大項,僅貨道支援與自身開關。 + binding.buttonLinePay.setVisibility( + (MyApp.getInstance().getDevSet().canLinePay() && slotField.isDevLinePay()) ? View.VISIBLE : View.GONE); - if (MyApp.getInstance().getDevSet().isDevTapPay()) { - if (slotField.isDevTapPay()) { - binding.buttonTapPay.setVisibility(View.VISIBLE); - } else { - binding.buttonTapPay.setVisibility(View.GONE); - } - } else { - binding.buttonTapPay.setVisibility(View.GONE); - } - - if (MyApp.getInstance().getDevSet().isDevCash()) { - if (slotField.isDevCash()) { - binding.buttonCash.setVisibility(View.VISIBLE); - } else { - binding.buttonCash.setVisibility(View.GONE); - } - } else { - binding.buttonCash.setVisibility(View.GONE); - } + // 現金:獨立大項。 + binding.buttonCash.setVisibility( + (MyApp.getInstance().getDevSet().canCash() && slotField.isDevCash()) ? View.VISIBLE : View.GONE); } catch (LogsEmptyException e) { - if (MyApp.getInstance().getDevSet().isDevNFCPay()) { -// binding.layoutNfcPay.setVisibility(View.VISIBLE); - } else { -// binding.layoutNfcPay.setVisibility(View.GONE); - } - - if (MyApp.getInstance().getDevSet().isDevEsunPay()) { - binding.buttonEsunPay.setVisibility(View.VISIBLE); - } else { - binding.buttonEsunPay.setVisibility(View.GONE); - } - - if (MyApp.getInstance().getDevSet().isDevLinePay()) { - binding.buttonLinePay.setVisibility(View.VISIBLE); - } else { - binding.buttonLinePay.setVisibility(View.GONE); - } - - if (MyApp.getInstance().getDevSet().isDevTapPay()) { - binding.buttonTapPay.setVisibility(View.VISIBLE); - } else { - binding.buttonTapPay.setVisibility(View.GONE); - } - - if (MyApp.getInstance().getDevSet().isDevCash()) { - binding.buttonCash.setVisibility(View.VISIBLE); - } else { - binding.buttonCash.setVisibility(View.GONE); - } - + // 無貨道資訊:略過貨道層級限制,只依「母&&子」開關判斷。 + binding.buttonNfcCard.setVisibility(MyApp.getInstance().getDevSet().canCreditCard() ? View.VISIBLE : View.GONE); + binding.buttonNfcRecharge.setVisibility(MyApp.getInstance().getDevSet().canCardPay() ? View.VISIBLE : View.GONE); + binding.buttonNfcPhone.setVisibility(MyApp.getInstance().getDevSet().canMobilePay() ? View.VISIBLE : View.GONE); + binding.buttonEsunPay.setVisibility(MyApp.getInstance().getDevSet().canEsunPay() ? View.VISIBLE : View.GONE); + binding.buttonTapPay.setVisibility(MyApp.getInstance().getDevSet().canTapPay() ? View.VISIBLE : View.GONE); + binding.buttonLinePay.setVisibility(MyApp.getInstance().getDevSet().canLinePay() ? View.VISIBLE : View.GONE); + binding.buttonCash.setVisibility(MyApp.getInstance().getDevSet().canCash() ? View.VISIBLE : View.GONE); } } } @@ -481,6 +421,13 @@ public class BuyDialog extends DialogAbstract { public void changeCheckout() { closeDialogCountdown.shutdown(); int flowType = MyApp.getInstance().getReportFlowInfoData().getFlowType(); + // 入口防呆:即使按鈕被繞過(殘留事件/外部觸發),進入支付 dialog 前再以同一份「母&&子」 + // 開關權威判斷一次;母開關或子項任一關閉即擋下,避免已停用的支付方式漏出。 + if (!isPaymentFlowAllowed(flowType)) { + getLogs().info("⛔ 支付方式已停用,封堵入口 flowType=" + flowType); + dialogCancel(); + return; + } switch (flowType) { case 1: changeDialog(CheckoutNfcCardDialog.class); @@ -512,6 +459,24 @@ public class BuyDialog extends DialogAbstract { } } + /** + * 入口防呆:依 flowType 對應的支付方式,判斷其「母&&子」開關是否允許。 + * flowType 5(通行碼)、7(來店禮)非支付方式,由取貨模組/來店禮各自把關,此處放行。 + */ + private boolean isPaymentFlowAllowed(int flowType) { + DevSetStructure d = MyApp.getInstance().getDevSet(); + switch (flowType) { + case 1: return d.canCreditCard(); // 信用卡(刷卡機) + case 2: return d.canCardPay(); // 電子票證/卡片支付(刷卡機) + case 4: return d.canMobilePay(); // 手機支付(刷卡機) + case 3: return d.canEsunPay(); // 玉山(掃碼) + case 30: return d.canTapPay(); // TapPay(掃碼) + case 70: return d.canLinePay(); // Line 官方支付(獨立) + case 9: return d.canCash(); // 現金(獨立) + default: return true; // 5/7 等非支付流程放行 + } + } + public static class CloseDialogOnThreadHandler extends HandlerMainCountdown { public CloseDialogOnThreadHandler(HandlerMain handlerMain) { super(handlerMain);