[REFACTOR] 取貨口閘門由進場改為付款前檢查,並修正發版腳本與語系下發

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) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-17 08:43:09 +08:00
parent f905380597
commit 84c7ac8933
7 changed files with 103 additions and 135 deletions

View File

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

View File

@ -10,7 +10,7 @@ plugins {
//
// =========================================================================
def verMinor = 2
def verPatch = 3
def verPatch = 5

View File

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

View File

@ -357,101 +357,6 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter<RecyclerVmcBu
}
}
// ===== 購物車模式專用進入銷售頁時的取貨口總閘門 =====
// 與員工卡點擊用的 checkSlotHaveProduct / DevCheckSlotByVMC 完全獨立不影響線上 main(晟崴/中國醫)流程
// 取貨口有東西(0x05) 門沒關(0x06/0x09) 時跳提示使用者確認後執行 onBlocked回待機
// 機器無回應(3000ms 超時) 視為正常放行避免卡死
public void checkPickupPortOnEntry(int slot, Runnable onBlocked) {
java.util.concurrent.atomic.AtomicBoolean isHandled = new java.util.concurrent.atomic.AtomicBoolean(false);
new Handler(Looper.getMainLooper()).postDelayed(() -> {
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;

View File

@ -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() {

View File

@ -259,6 +259,92 @@ public class BuyDialog extends DialogAbstract {
binding.buttonCash.setVisibility(MyApp.getInstance().getDevSet().canCash() ? View.VISIBLE : View.GONE);
}
}
// 準備付款時檢查取貨口basic 模式取貨口有東西(0x05)/門沒關(0x060x09) 就提示並關閉付款頁回銷售頁
// 員工卡(點擊已自檢)取貨碼(自有檢查)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);
}
});
}
}
/** 商品原始總額(未套用來店禮折扣),不含免費商品。 */

View File

@ -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" />
</LinearLayout>