From 84c7ac893346f3dff86fd8b2147c0b5b8a17271e Mon Sep 17 00:00:00 2001 From: sky121113 Date: Wed, 17 Jun 2026 08:43:09 +0800 Subject: [PATCH] =?UTF-8?q?[REFACTOR]=20=E5=8F=96=E8=B2=A8=E5=8F=A3?= =?UTF-8?q?=E9=96=98=E9=96=80=E7=94=B1=E9=80=B2=E5=A0=B4=E6=94=B9=E7=82=BA?= =?UTF-8?q?=E4=BB=98=E6=AC=BE=E5=89=8D=E6=AA=A2=E6=9F=A5=EF=BC=8C=E4=B8=A6?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=99=BC=E7=89=88=E8=85=B3=E6=9C=AC=E8=88=87?= =?UTF-8?q?=E8=AA=9E=E7=B3=BB=E4=B8=8B=E7=99=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 取貨口閘門時機調整(basic 模式):移除「進入銷售頁」的總閘門,改在「準備付款時」於 BuyDialog 檢查。 - VmcFragment:移除 maybeRunPickupPortEntryGate 與 entryGateChecked,startOnSwitchFragment 還原為僅更新清單。 - RecyclerVmcBuyListAdpter:移除 checkPickupPortOnEntry 與 DevEntryGateByVMC 整段(進場閘門)。 - BuyDialog:新增 checkPickupPortBeforePay 與 DevCheckPickupByVMC,取貨口有東西(0x05)或門沒關(0x06/0x09)時提示, 使用者確認後以 Option.FORCE_CLOSE 關閉付款頁回銷售頁;機器無回應(3000ms 超時)視為正常放行。 2. StarCloudAPI 語系下發改為後台優先:每次下發機台預設語系即立即套用,覆蓋使用者先前手動選擇 (設定下發頻率低且皆在無人時段,不會打斷操作)。 3. dialog_buy.xml:選擇支付方式頁的 Line Pay 按鈕文字由「掃碼支付」改為「Line Pay 支付」。 4. app/build.gradle:verPatch 4 → 5(S9XYU 正式發版,versionCode 210205 / versionName 21_02_5_R)。 5. release-apk workflow:修正終止 Gradle Daemon 段落的腳本自殺 bug,比對字串改為拆字串拼接並以 grep -vx "$$" 排除自身 PID,避免背景任務中 pgrep -f 命中腳本本身導致 exit 144。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .agents/workflows/release-apk.md | 15 ++- app/build.gradle | 2 +- .../smartdevice/external/StarCloudAPI.java | 7 +- .../ui/devs/vmc/RecyclerVmcBuyListAdpter.java | 95 ------------------- .../smartdevice/ui/devs/vmc/VmcFragment.java | 31 +----- .../smartdevice/ui/dialog/BuyDialog.java | 86 +++++++++++++++++ app/src/main/res/layout/dialog_buy.xml | 2 +- 7 files changed, 103 insertions(+), 135 deletions(-) diff --git a/.agents/workflows/release-apk.md b/.agents/workflows/release-apk.md index 04714bd..f2c8dc7 100644 --- a/.agents/workflows/release-apk.md +++ b/.agents/workflows/release-apk.md @@ -33,13 +33,20 @@ description: 自動編譯正式發行 Release APK、升級 Patch 版本號、並 // turbo # 終止殘留的 Gradle Daemon 程序,避免多個 Daemon 互搶資源,同時避免誤殺 VS Code Gradle 擴充功能 # 防呆:先優雅 --stop;僅在仍殘留時才升級終止(先 TERM 再 KILL),避免無差別強殺撞鎖導致 clean 編譯隨機失敗 +# +# ⚠️ 關鍵:pgrep/pkill 的比對字串必須「拆開」拼接,不可在本檔字面寫出完整 class 名。 +# 否則在背景任務(bash -c "...eval '整段腳本'...")情境下,腳本自己的命令列會含有 +# 完整的 GradleDaemon class 字串 → pgrep -f 比對到自己 → pkill 把跑腳本的 bash 自殺 +# → exit 144,編譯根本還沒開始。再加 -vx $$ 排除自身 PID 雙重保險。 +DAEMON_PAT='org.gradle.launcher.daemon.bootstrap''.GradleDaemon' # 字面被拆開,pgrep 不會命中本腳本 +daemon_alive() { pgrep -f "$DAEMON_PAT" 2>/dev/null | grep -vx "$$" | grep -q .; } ./gradlew --stop 2>/dev/null || true sleep 2 -if pgrep -f "org.gradle.launcher.daemon.bootstrap.GradleDaemon" >/dev/null 2>&1; then - pkill -TERM -f "org.gradle.launcher.daemon.bootstrap.GradleDaemon" 2>/dev/null || true +if daemon_alive; then + pkill -TERM -f "$DAEMON_PAT" 2>/dev/null || true sleep 3 - if pgrep -f "org.gradle.launcher.daemon.bootstrap.GradleDaemon" >/dev/null 2>&1; then - pkill -9 -f "org.gradle.launcher.daemon.bootstrap.GradleDaemon" 2>/dev/null || true + if daemon_alive; then + pkill -9 -f "$DAEMON_PAT" 2>/dev/null || true sleep 2 fi fi diff --git a/app/build.gradle b/app/build.gradle index 199be5a..2c3f175 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 2 -def verPatch = 3 +def verPatch = 5 diff --git a/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java b/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java index 50ce796..90feb96 100644 --- a/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java +++ b/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java @@ -1104,10 +1104,9 @@ public class StarCloudAPI { : com.unibuy.smartdevice.utils.LanguageHelper.canonicalize(def); MyApp.getInstance().setDefaultLanguage(canonicalDef); - // 首次(使用者尚未手動選過語言)套用機台預設語系 - android.content.SharedPreferences prefs = handlerMain.getContext() - .getSharedPreferences("app_prefs", android.content.Context.MODE_PRIVATE); - if (!canonicalDef.isEmpty() && prefs.getString("language", null) == null) { + // 後台優先:每次下發都以機台預設語系為準,立即套用(覆蓋使用者先前手動選擇)。 + // 設定下發頻率極低且皆在無人時段,不會打斷使用者操作。 + if (!canonicalDef.isEmpty()) { com.unibuy.smartdevice.utils.LanguageHelper.changeLanguage(handlerMain.getContext(), canonicalDef); } } 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 30bffb0..ff04fdd 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 @@ -357,101 +357,6 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter { - if (!isHandled.getAndSet(true)) { - logs.info("Entry Gate Check Timeout - 放行購物 (Timeout Safeguard)"); - } - }, 3000); - - MyApp.getInstance().getDevXinYuanController().addSendBuffer( - MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlotPre(slot, 1)); - MyApp.getInstance().getDevXinYuanController().addSendBuffer( - MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot)); - MyApp.getInstance().getDevXinYuanController().getReadBufferByNow( - new DevEntryGateByVMC(fragment.getSrcHandlerMain(), isHandled, onBlocked), 800); - } - - public class DevEntryGateByVMC extends DevController.ReadBufferOnScheduler { - private final java.util.concurrent.atomic.AtomicBoolean isHandled; - private final Runnable onBlocked; - - public DevEntryGateByVMC(HandlerMain handlerMain, - java.util.concurrent.atomic.AtomicBoolean isHandled, Runnable onBlocked) { - super(handlerMain); - this.isHandled = isHandled; - this.onBlocked = onBlocked; - } - - @Override - protected HandlerMain setHandlerMain() { - return getSrcHandlerMain(); - } - - @Override - protected Class setCls() { - return getClass(); - } - - @Override - public void readBufferOnScheduler(DevController devController, HandlerMain handlerMain) { - boolean haveThings = false; - boolean doorError = false; - for (byte[] buffer : devController.getReadBufferByMax()) { - if (buffer[0] == 0x02) { - if (buffer[1] == 0x05) { - haveThings = true; - break; - } else if (buffer[1] == 0x06 || buffer[1] == 0x09) { - doorError = true; - break; - } - } - } - - // 狀態安全:不做任何事,放行購物(總閘門通過) - if (!haveThings && !doorError) return; - // 已被超時或前一筆回覆處理過,避免重複跳窗 - if (isHandled.getAndSet(true)) return; - - final String message = doorError - ? "取貨口門未正常關閉,請先確認門已關好。" - : context.getString(R.string.there_is_something_at_the_pickup_port); - - new Handler(Looper.getMainLooper()).post(() -> { - AlertDialog dialog = new AlertDialog.Builder(context) - .setTitle(R.string.abnormal_reminder) - .setMessage(message) - .setCancelable(false) - .setPositiveButton(R.string.i_knew, (d, which) -> { - d.dismiss(); - if (onBlocked != null) onBlocked.run(); - }) - .create(); - dialog.show(); - - TextView messageView = dialog.findViewById(android.R.id.message); - if (messageView != null) { - messageView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24); - } - - // 10 秒後自動回待機,避免無人理會卡在提示 - new Handler(Looper.getMainLooper()).postDelayed(() -> { - if (dialog.isShowing()) { - dialog.dismiss(); - if (onBlocked != null) onBlocked.run(); - } - }, 10_000); - }); - } - } - public class DevDetectSlotByVMC extends DevController.ReadBufferOnScheduler { private int position; diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/VmcFragment.java b/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/VmcFragment.java index 22e1601..16ae582 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/VmcFragment.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/VmcFragment.java @@ -313,37 +313,8 @@ public class VmcFragment extends FragmentAbstract { || product.getSellingPrice() < 0; } - private boolean entryGateChecked = false; - @Override - public void startOnSwitchFragment() { - updateFlowerBuyList(); - maybeRunPickupPortEntryGate(); - } - - /** - * basic 模式(shopping_mode==basic,不論購物車開關)進入銷售頁時的「取貨口總閘門」: - * 取貨口有東西或門沒關就跳提示並回待機,確保「加入購物車 / 直接購買 / 快速加購 / 單品直購」 - * 各路徑在源頭被擋住。每輪購物只檢查一次(FontendActivity 每輪重建 fragment,旗標自動重置)。 - * 員工卡(employee_card)流程已在點擊時自行檢查、pickup_sheet 不檢查,皆不在此重複。 - */ - private void maybeRunPickupPortEntryGate() { - if (entryGateChecked) return; - boolean isBasic = "basic".equals(MyApp.getInstance().getShoppingMode()); - if (!isBasic) return; - if (recyclerVmcBuyListAdpter == null) return; - entryGateChecked = true; - - // 延遲送出,等下位機(新源 VMC)連線就緒;過早送出會超時放行而失去保護。 - new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> { - int gateSlot = (slotList != null && !slotList.isEmpty()) ? slotList.get(0).getSlot() : 1; - recyclerVmcBuyListAdpter.checkPickupPortOnEntry(gateSlot, () -> { - if (getActivity() instanceof FontendActivity) { - ((FontendActivity) getActivity()).ReSetAPP(); - } - }); - }, 2500); - } + public void startOnSwitchFragment() { updateFlowerBuyList(); } @Override public void stopOnSwitchFragment() { 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 c7f9081..7004621 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 @@ -259,6 +259,92 @@ public class BuyDialog extends DialogAbstract { binding.buttonCash.setVisibility(MyApp.getInstance().getDevSet().canCash() ? View.VISIBLE : View.GONE); } } + + // 準備付款時檢查取貨口(basic 模式):取貨口有東西(0x05)/門沒關(0x06、0x09) 就提示並關閉付款頁回銷售頁。 + // 員工卡(點擊已自檢)、取貨碼(自有檢查)、pickup_sheet 不在此檢查。 + if ("basic".equals(MyApp.getInstance().getShoppingMode())) { + checkPickupPortBeforePay(); + } + } + + // 準備付款時檢查取貨口(basic 模式)。送 checkSlot 後 800ms 讀回;此時距實際出貨還有數秒 + // (使用者尚未選付款),不會與出貨流程搶同一條 DevXinYuanController。機器無回應(3000ms 超時)視為正常放行。 + private void checkPickupPortBeforePay() { + java.util.concurrent.atomic.AtomicBoolean isHandled = new java.util.concurrent.atomic.AtomicBoolean(false); + new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed( + () -> isHandled.getAndSet(true), 3000); + MyApp.getInstance().getDevXinYuanController().addSendBuffer( + MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlotPre(1, 1)); + MyApp.getInstance().getDevXinYuanController().addSendBuffer( + MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(1)); + MyApp.getInstance().getDevXinYuanController().getReadBufferByNow( + new DevCheckPickupByVMC(getHandlerMain(), isHandled), 800); + } + + private class DevCheckPickupByVMC extends com.unibuy.smartdevice.controller.DevController.ReadBufferOnScheduler { + private final java.util.concurrent.atomic.AtomicBoolean isHandled; + private final HandlerMain dialogHandler; + + DevCheckPickupByVMC(HandlerMain handlerMain, java.util.concurrent.atomic.AtomicBoolean isHandled) { + super(handlerMain); + this.dialogHandler = handlerMain; + this.isHandled = isHandled; + } + + @Override + protected HandlerMain setHandlerMain() { + return dialogHandler; + } + + @Override + protected Class setCls() { + return getClass(); + } + + @Override + public void readBufferOnScheduler(com.unibuy.smartdevice.controller.DevController devController, HandlerMain handlerMain) { + boolean haveThings = false; + boolean doorError = false; + for (byte[] buffer : devController.getReadBufferByMax()) { + if (buffer[0] == 0x02) { + if (buffer[1] == 0x05) { + haveThings = true; + break; + } else if (buffer[1] == 0x06 || buffer[1] == 0x09) { + doorError = true; + break; + } + } + } + + // 狀態安全:放行付款 + if (!haveThings && !doorError) return; + // 已被超時或前一筆處理過,避免重複跳窗 + if (isHandled.getAndSet(true)) return; + + final String message = doorError + ? "取貨口門未正常關閉,請先確認門已關好。" + : BuyDialog.this.getContext().getString(R.string.there_is_something_at_the_pickup_port); + + new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> { + android.app.AlertDialog dialog = new android.app.AlertDialog.Builder(BuyDialog.this.getContext()) + .setTitle(R.string.abnormal_reminder) + .setMessage(message) + .setCancelable(false) + .setPositiveButton(R.string.i_knew, (d, which) -> { + d.dismiss(); + // 關閉付款頁回銷售頁 + dialogHandler.start("BuyDialogPickupCheck", + Option.FORCE_CLOSE.getOption(), "pickup port blocked"); + }) + .create(); + dialog.show(); + android.widget.TextView mv = dialog.findViewById(android.R.id.message); + if (mv != null) { + mv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 24); + } + }); + } } /** 商品原始總額(未套用來店禮折扣),不含免費商品。 */ diff --git a/app/src/main/res/layout/dialog_buy.xml b/app/src/main/res/layout/dialog_buy.xml index 53b7f57..53224b9 100644 --- a/app/src/main/res/layout/dialog_buy.xml +++ b/app/src/main/res/layout/dialog_buy.xml @@ -322,7 +322,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:text="掃碼支付" + android:text="Line Pay 支付" android:textColor="@color/black" android:textSize="25sp" />