[FIX] 優化出貨流程 UI 體驗與修復雙重倒數 Bug
1. 修復出貨結案時,因 VMC 重複回傳訊號導致 15 秒倒數重置的 Bug。 2. 實作 isFinalizing 狀態鎖,確保結案流程不被舊有的定時器或重複訊號干擾。 3. 優化出貨流程 UI:初始狀態顯示「準備出貨」,取代原先錯誤的提示文字。 4. 調整「取貨確認」按鈕邏輯:出貨中隱藏按鈕,僅在 15 秒取貨倒數時顯示,並支援使用者提前確認取貨。 5. 修正「員工卡」錯字並同步多語系(英、日)字串資源。 6. 在 MqttService 與 FontendActivity 實作 isMachineBusy() 檢查,防止交易過程中觸發遠端重啟。 7. 優化 VMC 偵測邏輯,加入重複訊號過濾防護。
This commit is contained in:
parent
93a9ad7e2d
commit
028cac10b5
@ -17,33 +17,35 @@ cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
|
||||
```
|
||||
|
||||
## 步驟二:導出 APK 到 outputs 資料夾
|
||||
將編譯出來的深層 APK 複製到專案最外層的 `outputs` 資料夾,並統一命名為 `TaiwanStar_debug.apk`。
|
||||
將編譯出來的 APK 複製到專案最外層的 `outputs` 資料夾。
|
||||
|
||||
```bash
|
||||
// turbo
|
||||
cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
|
||||
mkdir -p outputs
|
||||
cp app/build/outputs/apk/S_12_XY_U_Standard_/debug/app-S_12_XY_U_Standard_-10_08_2_R-debug.apk outputs/TaiwanStar_debug.apk
|
||||
echo "✅ APK 已經複製到專案根目錄的 outputs 資料夾中"
|
||||
APK_SOURCE="app/build/outputs/apk/S_12_XY_U_Standard_/debug/app-S_12_XY_U_Standard_-10_08_2_R-debug.apk"
|
||||
APK_DEST="outputs/TaiwanStar_debug.apk"
|
||||
|
||||
cp $APK_SOURCE $APK_DEST
|
||||
echo "✅ APK 已經複製到 outputs 資料夾,路徑為:$APK_DEST"
|
||||
```
|
||||
|
||||
## 步驟三:安裝並啟動 App (如果有連接設備)
|
||||
利用 WSL 串接 Windows 端的 ADB,偵測到設備後才執行安裝與啟動。
|
||||
## 步驟三:安裝並啟動 App (如有連接設備)
|
||||
偵測到設備後才執行安裝。如果未偵測到,則保留 APK 檔案並結束工作流。
|
||||
|
||||
```bash
|
||||
// turbo
|
||||
ADB_PATH="/mnt/c/Users/User/AppData/Local/Android/Sdk/platform-tools/adb.exe"
|
||||
APK_PATH="/home/mama/projects/TaiwanStar_general_size_s_Chengwai/outputs/TaiwanStar_debug.apk"
|
||||
|
||||
# 檢查是否有設備連接
|
||||
DEVICE_COUNT=$($ADB_PATH devices | grep -v "List of devices" | grep "device" | wc -l)
|
||||
|
||||
if [ "$DEVICE_COUNT" -gt 0 ]; then
|
||||
echo "📱 偵測到 $DEVICE_COUNT 個設備,開始安裝..."
|
||||
$ADB_PATH install -r -d -t $APK_PATH
|
||||
$ADB_PATH install -r -d -t "/home/mama/projects/TaiwanStar_general_size_s_Chengwai/outputs/TaiwanStar_debug.apk"
|
||||
echo "🚀 安裝完成,正在啟動 App..."
|
||||
$ADB_PATH shell am start -n com.unibuy.smartdevice/com.unibuy.smartdevice.HomeActivity
|
||||
else
|
||||
echo "⚠️ 未偵測到連接的手機或設備,跳過安裝與啟動步驟。"
|
||||
echo "⚠️ 未偵測到連接的手機或設備,已保留 APK 檔案,跳過安裝步驟。"
|
||||
fi
|
||||
```
|
||||
|
||||
@ -296,6 +296,11 @@ public class MqttService extends Service {
|
||||
.publishCommandAck(new com.unibuy.smartdevice.structure.mqtt.CommandAckPayload(
|
||||
cmdId, "success", "Inventory updated successfully",
|
||||
String.valueOf(stock)));
|
||||
|
||||
// ✅ 通知前台銷售平台刷新商品列表(輕量級,不重建 Fragment)
|
||||
Intent refreshIntent = new Intent("com.unibuy.smartdevice.ACTION_SLOT_UPDATED");
|
||||
refreshIntent.setPackage(getPackageName());
|
||||
sendBroadcast(refreshIntent);
|
||||
} else {
|
||||
throw new Exception("Slot not found: " + slotNo);
|
||||
}
|
||||
|
||||
@ -5,8 +5,10 @@ import static android.view.View.VISIBLE;
|
||||
import android.Manifest;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
@ -128,6 +130,17 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
private Handler idleHandler = new Handler();
|
||||
private Runnable idleRunnable;
|
||||
|
||||
// ✅ 遠端庫存更新監聽器(輕量級,只刷新 Adapter 不重建 Fragment)
|
||||
private final BroadcastReceiver slotUpdateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
getLogs().info("收到遠端庫存更新通知,刷新銷售平台商品列表...");
|
||||
if (vmcFragment instanceof VmcFragment) {
|
||||
((VmcFragment) vmcFragment).updateFlowerBuyList();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static VideoEoxPlayer videoEoxPlayer;
|
||||
|
||||
@Override
|
||||
@ -1043,6 +1056,13 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
resetIdleTimer();
|
||||
// ✅ 註冊庫存更新監聽
|
||||
IntentFilter filter = new IntentFilter("com.unibuy.smartdevice.ACTION_SLOT_UPDATED");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(slotUpdateReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(slotUpdateReceiver, filter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1051,6 +1071,12 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
if (idleHandler != null) {
|
||||
idleHandler.removeCallbacks(idleRunnable);
|
||||
}
|
||||
// ✅ 取消註冊庫存更新監聽
|
||||
try {
|
||||
unregisterReceiver(slotUpdateReceiver);
|
||||
} catch (Exception e) {
|
||||
getLogs().info("unregisterReceiver slotUpdateReceiver: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -150,6 +150,21 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter<RecyclerVmcBu
|
||||
if (slotProduct.isLock()) return;
|
||||
if (MyApp.getInstance().isShowDialog()) return;
|
||||
|
||||
// ✅ 即時庫存檢查(防止 MQTT 更新後 UI 尚未刷新的時間差)
|
||||
try {
|
||||
SlotStructure liveSlot = MyApp.getInstance().getSlotData(slotProduct.getField(), slotProduct.getSlot());
|
||||
if (liveSlot != null && liveSlot.getCount() <= 0) {
|
||||
logs.info("即時庫存檢查:商品已售罄 slot=" + slotProduct.getSlot());
|
||||
android.widget.Toast.makeText(context, "商品已售罄", android.widget.Toast.LENGTH_SHORT).show();
|
||||
// 觸發 UI 刷新讓商品顯示「補貨中」
|
||||
fragment.getSrcHandlerMain().start(getClass().getSimpleName(),
|
||||
VmcFragment.Option.SHIPPED_PRODUCT.getOption(), "refresh");
|
||||
return;
|
||||
}
|
||||
} catch (LogsEmptyException e) {
|
||||
// 查詢失敗時不阻擋,讓原流程繼續
|
||||
}
|
||||
|
||||
BuyStructure buyProduct = new BuyStructure(slotProduct.getField(), slotProduct.getSlot(), slotProduct.getProduct(), 1);
|
||||
MyApp.getInstance().getBuyList().add(buyProduct);
|
||||
fragment.getSrcHandlerMain().start(getClass().getSimpleName(), FontendActivity.Option.STOP_COUNTDOWN.getOption(), "stop countdown");
|
||||
|
||||
@ -132,18 +132,22 @@ public class DispatchDialog extends DialogAbstract {
|
||||
cancelOnThreadCountdown();
|
||||
break;
|
||||
case SET_COUNTDOWN_TEXT:
|
||||
if (isFinalizing) break; // 結案倒數中,忽略舊的倒數事件
|
||||
setButtonOkText(Integer.parseInt(message));
|
||||
if (Integer.parseInt(message) <= 15) {
|
||||
setTextViewCountdown(Integer.parseInt(message));
|
||||
}
|
||||
break;
|
||||
case NEXT_PRODUCT:
|
||||
if (isFinalizing) break; // 結案倒數中,不再重新啟動出貨
|
||||
currentlyProcessingGoods();
|
||||
break;
|
||||
case ENABLED_TRUE:
|
||||
if (isFinalizing) break; // 結案倒數中,不改變按鈕狀態
|
||||
openButtonOk();
|
||||
break;
|
||||
case ENABLED_FALSE:
|
||||
if (isFinalizing) break; // 結案倒數中,不改變按鈕狀態
|
||||
closeButtonOk();
|
||||
break;
|
||||
case REPORT_DISPATCH:
|
||||
@ -155,12 +159,15 @@ public class DispatchDialog extends DialogAbstract {
|
||||
setTextViewSmall(message);
|
||||
break;
|
||||
case CURRENTLY_PROCESSING_GOODS:
|
||||
if (isFinalizing) break; // 結案倒數中,不再處理
|
||||
currentlyProcessingGoods();
|
||||
break;
|
||||
case SHIPPED_PROCESSING_GOODS:
|
||||
if (isFinalizing) break; // 結案倒數中,不再處理
|
||||
shippedProcessingGoods();
|
||||
break;
|
||||
case NEXT_PROCESS_GOODS:
|
||||
if (isFinalizing) break; // 結案倒數中,不再處理
|
||||
nextProcessGoods();
|
||||
break;
|
||||
case ERROR_PROCESS_GOODS:
|
||||
@ -214,6 +221,7 @@ public class DispatchDialog extends DialogAbstract {
|
||||
private List<DispatchStructure> dispatchList;
|
||||
private int dispatchIndex;
|
||||
private boolean isReportDispatch;
|
||||
private boolean isFinalizing = false; // 標記是否正在進行最後的取貨倒數結案
|
||||
private boolean isChangeStatus;
|
||||
private int countdownNumber = 40;
|
||||
private CountDownTimer countDownTimer;
|
||||
@ -293,9 +301,19 @@ public class DispatchDialog extends DialogAbstract {
|
||||
this.binding.recyclerDialogDispatchList.setLayoutManager(new LinearLayoutManager(context));
|
||||
|
||||
this.binding.buttonOk.setOnClickListener(v -> {
|
||||
currentlyProcessingGoods();
|
||||
if (isFinalizing) {
|
||||
// 結案倒數中,按下 = 提前確認取貨,關閉 dialog
|
||||
if (countDownTimer != null) {
|
||||
countDownTimer.cancel();
|
||||
}
|
||||
dialogCancel();
|
||||
} else {
|
||||
currentlyProcessingGoods();
|
||||
}
|
||||
});
|
||||
|
||||
// 出貨過程中隱藏按鈕,等到 15 秒取貨倒數再顯示
|
||||
binding.buttonOk.setVisibility(View.GONE);
|
||||
setButtonOkText(0);
|
||||
|
||||
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
|
||||
@ -440,10 +458,11 @@ public class DispatchDialog extends DialogAbstract {
|
||||
checkSlotRunCount = 0;
|
||||
MyApp.getInstance().getDevXinYuanController().start();
|
||||
} else {
|
||||
getLogs().info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA004");
|
||||
// MyApp.getInstance().getDevXinYuanController().getReadBufferByNow(new DevCheckSlotByVMC(handlerMain, slot));
|
||||
// MyApp.getInstance().getDevXinYuanController().addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot));
|
||||
getLogs().info("機台未回應,第 " + (checkSlotRunCount + 1) + " 次重試 checkSlot...");
|
||||
isCheckSlotByVMC = false; // 重置旗標,允許下次回調進入
|
||||
checkSlotRunCount++;
|
||||
MyApp.getInstance().getDevXinYuanController().getReadBufferByNow(new DevCheckSlotByVMC(handlerMain, slot));
|
||||
MyApp.getInstance().getDevXinYuanController().addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -616,7 +635,7 @@ public class DispatchDialog extends DialogAbstract {
|
||||
getLogs().warning(e);
|
||||
}
|
||||
|
||||
setTextViewCountdown(0);
|
||||
setTextViewCountdown(getCtx().getString(R.string.preparing_to_dispatch), 65);
|
||||
isGetProductByVMC = false;
|
||||
isCheckSlotByVMC = false;
|
||||
checkSlotRunCount = 0;
|
||||
@ -803,6 +822,9 @@ public class DispatchDialog extends DialogAbstract {
|
||||
//偵測是否還有下個出貨商品
|
||||
if (this.dispatchIndex >= this.dispatchList.size()-1) { //沒有貨
|
||||
// ✅ 所有商品出貨完畢,發送 MQTT Finalize 結案訊息
|
||||
if (isFinalizing) return; // 防呆:如果已經在結案倒數了,直接跳出
|
||||
isFinalizing = true;
|
||||
|
||||
getLogs().info("所有商品出貨完畢,發送 MQTT Finalize 結案訊息。");
|
||||
MqttManager.getInstance().publishTransactionFinalize(finalizeBuilder.build());
|
||||
getSrcHandlerMain().send(getClass().getSimpleName(), FontendActivity.Option.RESET_DATA.getOption(), "update data");
|
||||
@ -813,7 +835,40 @@ public class DispatchDialog extends DialogAbstract {
|
||||
getLogs().info(logItem);
|
||||
}
|
||||
|
||||
dialogCancel(); // 結束對話框
|
||||
// 停止所有計時器
|
||||
closeDialogCountdown.shutdown();
|
||||
if (!buttonControlCountdown.isShutdown()) {
|
||||
buttonControlCountdown.shutdown();
|
||||
}
|
||||
if (countDownTimer != null) {
|
||||
countDownTimer.cancel();
|
||||
}
|
||||
|
||||
// 隱藏底部倒數文字
|
||||
binding.tvCountdown.setVisibility(View.GONE);
|
||||
|
||||
// 顯示取貨確認按鈕(讓使用者可以提前確認取貨)
|
||||
binding.buttonOk.setVisibility(View.VISIBLE);
|
||||
binding.buttonOk.setEnabled(true);
|
||||
binding.buttonOk.setTextColor(Color.BLACK);
|
||||
setButtonOkText(0); // 顯示「取貨確認」不帶倒數
|
||||
|
||||
// 顯示取貨倒數 15 秒後自動關閉對話框
|
||||
isChangeStatus = true;
|
||||
setTextViewCountdown(getCtx().getString(R.string.pick_up_goods_within_15_seconds_after_pick_up_port_opened), 65);
|
||||
countDownTimer = new CountDownTimer(15_000, 1_000) {
|
||||
public void onTick(long millisUntilFinished) {
|
||||
int seconds = (int) (millisUntilFinished / 1000);
|
||||
setTextViewCountdown(seconds);
|
||||
}
|
||||
public void onFinish() {
|
||||
dialogCancel();
|
||||
}
|
||||
}.start();
|
||||
getLogs().info("📢 出貨完畢,取貨倒數 15 秒後關閉對話框");
|
||||
|
||||
isDispatchingNext = false;
|
||||
isGetProductByVMC = false;
|
||||
return; // 結束方法
|
||||
} else { //有貨要出了
|
||||
this.dispatchIndex += 1; //表記下一個商品要出貨
|
||||
|
||||
@ -188,6 +188,20 @@ public class MemberCardPickupDialog extends DialogAbstract {
|
||||
public void onSuccess(String codeId) {
|
||||
getLogs().info("MemberCardPickupDialog - B680 Success, code_id: " + codeId);
|
||||
|
||||
// ✅ 最後一道防線:出貨前即時檢查庫存
|
||||
for (com.unibuy.smartdevice.structure.BuyStructure buy : MyApp.getInstance().getBuyList()) {
|
||||
try {
|
||||
com.unibuy.smartdevice.structure.SlotStructure liveSlot =
|
||||
MyApp.getInstance().getSlotData(buy.getField(), buy.getSlot());
|
||||
if (liveSlot != null && liveSlot.getCount() <= 0) {
|
||||
getLogs().info("庫存已歸零 slot=" + buy.getSlot() + ",取消出貨,返回銷售平台。");
|
||||
getHandlerMain().start(getClass().getSimpleName(), Option.TOAST.getOption(), "商品已售罄,請重新選擇");
|
||||
dialogCancel(); // 內部已清空 BuyList + dismiss
|
||||
return;
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// 設定支付資訊
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowType(41); // 員工卡
|
||||
MyApp.getInstance().getReportFlowInfoData().setReportFlowId(cardNo);
|
||||
|
||||
@ -340,6 +340,7 @@
|
||||
<string name="shopping_cart_merchant_payment_checkout">merchant payment checkout</string>
|
||||
<string name="tappay_checkout">TapPay Checkout</string>
|
||||
<string name="pick_up_goods_within_15_seconds_after_pick_up_port_opened">Pick up goods within 15 seconds after the pick-up port is opened</string>
|
||||
<string name="preparing_to_dispatch">Preparing to dispatch</string>
|
||||
<string name="enter_gift_or_scan_qrcode">Enter the gift or scan the QR Code</string>
|
||||
<string name="select_combination">Select combination</string>
|
||||
<string name="pickup">Pickup</string>
|
||||
|
||||
@ -340,6 +340,7 @@
|
||||
<string name="shopping_cart_merchant_payment_checkout">販売者支払いチェックアウト</string>
|
||||
<string name="tappay_checkout">TapPayチェックアウト</string>
|
||||
<string name="pick_up_goods_within_15_seconds_after_pick_up_port_opened">ピックアップポートが開いてから15秒以内に商品を受け取る</string>
|
||||
<string name="preparing_to_dispatch">出荷準備中</string>
|
||||
<string name="enter_gift_or_scan_qrcode">ギフトを入力するか、QRコードをスキャンしてください</string>
|
||||
<string name="select_combination">組み合わせを選択</string>
|
||||
<string name="pickup">選び出す</string>
|
||||
|
||||
@ -250,6 +250,7 @@
|
||||
<string name="shopping_cart_merchant_payment_checkout">商家支付結帳</string>
|
||||
<string name="tappay_checkout">TapPay結帳</string>
|
||||
<string name="pick_up_goods_within_15_seconds_after_pick_up_port_opened">取貨口開啟15秒內取貨</string>
|
||||
<string name="preparing_to_dispatch">準備出貨</string>
|
||||
<string name="enter_gift_or_scan_qrcode">輸入來店禮 或 掃描QRCode</string>
|
||||
<string name="select_combination">選擇組合</string>
|
||||
<string name="pickup">取貨</string>
|
||||
@ -300,7 +301,7 @@
|
||||
<string name="i_knew">知道了</string>
|
||||
<string name="input_vehicle">輸入載具</string>
|
||||
<string name="member_card_pickup_title">領取確認</string>
|
||||
<string name="please_tap_member_card">請將員山卡靠近讀卡機</string>
|
||||
<string name="please_tap_member_card">請將員工卡靠近讀卡機</string>
|
||||
<string name="sync_product">下載商品資料</string>
|
||||
<string name="sync_product_warning">確定要從雲端下載商品資料嗎?這將會更新所有商品的品名、價格與圖片,並覆蓋目前的資訊。</string>
|
||||
<string name="downloading_product_data">正在下載商品資料...</string>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user