[FEAT] 支付方式「母&&子」集中判斷 + 入口防呆;對話框期間隱藏銷售平台浮層

- DevSetStructure 新增 canCreditCard/canCardPay/canMobilePay/canEsunPay/
  canTapPay/canLinePay/canCash,集中「母開關 && 子項」真相
- BuyDialog 購物車/單品/例外三分支改用上述便利方法,並在 changeCheckout
  入口以 isPaymentFlowAllowed 再次防呆,擋下被繞過的已停用支付
- 浮層連動:HIDDEN/SHOW_MULTILANGUAGE_BUTTON 改為 HIDE/SHOW_SALE_OVERLAY,
  對話框與購物車 Fragment 期間統一隱藏取貨碼/購物車浮鈕與上下系統列
- 無操作逾時 3 分鐘調整為 5 分鐘;verPatch 79→80

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-11 16:58:14 +08:00
parent fc21186a53
commit 8d2adee5ab
5 changed files with 151 additions and 125 deletions

View File

@ -10,7 +10,7 @@ plugins {
//
// =========================================================================
def verMinor = 1
def verPatch = 79
def verPatch = 80

View File

@ -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");
}
}

View File

@ -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; }
}

View File

@ -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 類別

View File

@ -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());
// 貨道層級是否支援 NFCVMC/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);