feat: 出貨自救、登入gate、全站i18n補翻、硬幣修正、待機優化 (verPatch 40)

出貨/連線
- 出貨當下加連線閘門:送出貨指令前先確認下位機,失連則反覆自動重連
  直到活著或逾時(ensureXinYuanConnectedForDispatch),再送指令 → 真正補出貨
- 保留 checkSlot 重試輪當第二道保險(出貨中途掉線)
- 非現金付款進出貨前補送 inComeByIPC
- PortProfile 重構:引入 DEFAULT_PROFILE 常數(通用預設 LEGACY)

後台登入
- 進 MainActivity(含長按序號後門)一律先跳登入視窗,帳密正確才進主控台,
  取消則 finish() 返回前台

多國語(中/英/日)
- 商品名改用 getLocalizedName:出貨中畫面、取貨確認清單、取貨碼確認清單
- 補翻取貨口提示(門未關/升降/狀態確認中/檢查中)
- 補翻取貨碼頁(主提示/取消倒數/員工卡hint/驗證中/過效期/折扣類型)
- 補翻出貨列狀態(已出貨數量/出貨中X件/All)
- 發票方式頁「捐贈發票」→「捐贈發票/下一步」(新key,僅該卡片)

現金/硬幣
- 1元虛擬庫存封頂實體上限200(增量與載入均夾住)
- 投幣測試面板解析 COIN_STOCK/NOTE_STOCK 十進位(修1元>255破版)

出貨完成
- 有待機機台出貨完成後留數秒取貨提示即跳待機(CMUH/北護無待機維持原流程)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
terrylee 2026-07-13 17:20:58 +08:00
parent 954d4a3716
commit 1d11367066
55 changed files with 660 additions and 226 deletions

View File

@ -10,7 +10,7 @@ plugins {
//
// =========================================================================
def verMinor = 2
def verPatch = 37
def verPatch = 40

View File

@ -302,6 +302,12 @@ public class MainActivity extends AppCompatActivityAbstract {
}
});
// 進後台一律先登入未登入時第一時間跳出登入視窗(不可取消)輸入正確帳密才能操作主控台
// 取消則直接返回前台(finish)涵蓋所有進入 MainActivity 的入口(含長按序號的隱藏後門)
if (MyApp.getInstance().getSuAccess().isEmpty()) {
showLoginDialog(getCtx(), null, true);
}
}
private static class ButtonQrCodeTestOnClick extends HandlerMainSchedulerOnClick {
@ -522,10 +528,18 @@ public class MainActivity extends AppCompatActivityAbstract {
}
public void showLoginDialog(Context context) {
showLoginDialog(context, null);
showLoginDialog(context, null, false);
}
public void showLoginDialog(Context context, Runnable onSuccess) {
showLoginDialog(context, onSuccess, false);
}
/**
* @param mandatory true=進後台強制登入對話框不可取消(返回鍵/點外部無效)取消直接 finish 返回前台
* 未登入不得停留在主控台false=一般用途(取消僅關閉視窗)
*/
public void showLoginDialog(Context context, Runnable onSuccess, boolean mandatory) {
// 使用 LayoutInflater 載入自訂佈局
View view = LayoutInflater.from(context).inflate(R.layout.dialog_admin_login, null);
@ -550,6 +564,7 @@ public class MainActivity extends AppCompatActivityAbstract {
// 建立對話框
AlertDialog dialog = new AlertDialog.Builder(context)
.setView(view)
.setCancelable(!mandatory) // 強制登入時不可用返回鍵/點外部關閉
.create();
activeLoginDialog = dialog;
@ -596,6 +611,10 @@ public class MainActivity extends AppCompatActivityAbstract {
getTools().hideSystemBars((Activity) getCtx());
dialog.dismiss();
activeLoginDialog = null;
if (mandatory) {
// 強制登入下按取消 未登入不得進後台直接返回前台
finish();
}
});
dialog.show();

View File

@ -682,9 +682,15 @@ public class MyApp extends Application {
}
}
/** 連送同步包兩次,任一次有回應即視為連線,降低偶發誤判(單次沒回不代表失連)。 */
/**
* 下位機是否在線優先採信近期任何 VMC 通訊下位機為輪詢式主從持續在 POLL
* 只要近 4 秒收過任何 VMC (POLL/ACK/廣播)即代表在線直接判定連線
* 避免脆弱的 300ms 主動 ping 在機台忙碌/送出佇列消化時收不到即時回應而誤判尚未連線
* 近期完全沒通訊時才主動 ping 兩次( 500ms)確認降低偶發誤判單次沒回不代表失連
*/
public static boolean isXinYuanAlive() {
return pingXinYuanOnce(300) || pingXinYuanOnce(300);
if (com.unibuy.smartdevice.controller.DevXinYuanController.isRecentlyAlive(4000)) return true;
return pingXinYuanOnce(500) || pingXinYuanOnce(500);
}
/**
@ -698,6 +704,33 @@ public class MyApp extends Application {
return isXinYuanAlive(); // 重連後再確認
}
/**
* 出貨當下用出貨前最後一道保險確認主櫃(XinYuan)下位機連線
* 失連就反覆重連並等到活著直到成功或逾時 maxWaitMs
* 與進支付前的 ensureXinYuanConnected 不同這裡會多輪重連(每輪等重開串口/重啟讀取迴圈)
* 因為下位機常在重啟後短暫失聯單輪重連可能還沒起來需持續重試到真的活著才送出貨指令
* 回傳最終是否連線成功注意 Thread.sleep請在背景執行緒呼叫
*/
public static boolean ensureXinYuanConnectedForDispatch(long maxWaitMs) {
Logs staticLogs = new Logs(MyApp.class);
if (isXinYuanAlive()) return true;
long deadline = System.currentTimeMillis() + maxWaitMs;
int round = 0;
while (System.currentTimeMillis() < deadline) {
round++;
staticLogs.info("出貨前下位機失連,第 " + round + " 次自動重連...");
checkAndReconnectDev(); // 重建 controller重開串口重啟讀取迴圈
try { Thread.sleep(800); } catch (InterruptedException ignore) {}
if (isXinYuanAlive()) {
staticLogs.info("出貨前重連成功(第 " + round + " 輪),下位機已連線");
return true;
}
}
boolean alive = isXinYuanAlive();
staticLogs.info("出貨前重連結束,逾時=" + maxWaitMs + "ms最終連線=" + alive);
return alive;
}
public void initMqtt() {
String machineId = MyApp.getInstance().getMachine().getMachineID();
String apiKey = MyApp.getInstance().getMachine().getApiKey();

View File

@ -24,23 +24,27 @@ public final class PortProfile {
public static final String LEGACY = "legacy"; // 舊版本 ttyS1 / ttyS2
public static final String STARTECH = "startech"; // 星科技版本ttyS7 / ttyS6
// 未設定時的預設方案通用版本為 LEGACYttyS1/ttyS2
// ️代樂美等星科技主板專用建置需改為 STARTECHttyS7/ttyS6裝上即用免手動切該改動勿進通用主線
public static final String DEFAULT_PROFILE = LEGACY;
private PortProfile() {}
private static SharedPreferences sp(Context ctx) {
return ctx.getApplicationContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
}
/** 目前串口方案;預設 LEGACY。無 Context 版供 enum/背景使用。 */
/** 目前串口方案;未設定時採 DEFAULT_PROFILE。無 Context 版供 enum/背景使用。 */
public static String getProfile() {
try {
return sp(MyApp.getInstance()).getString(KEY_PROFILE, LEGACY);
return sp(MyApp.getInstance()).getString(KEY_PROFILE, DEFAULT_PROFILE);
} catch (Exception e) {
return LEGACY;
return DEFAULT_PROFILE;
}
}
public static String getProfile(Context ctx) {
return sp(ctx).getString(KEY_PROFILE, LEGACY);
return sp(ctx).getString(KEY_PROFILE, DEFAULT_PROFILE);
}
/** 是否為星科技方案(ttyS7/ttyS6)。 */

View File

@ -36,6 +36,9 @@ public class SerialCashAcceptorHandler implements ICashAcceptorHandler {
private int lastCoinReg0Val = 0;
private int lastEscrowRegVal = 0;
public static int virtualCoin1Count = 30;
// 1 元料斗實體上限1 元硬幣機構為軟體虛擬計數(硬體不回報)只增不減易飄成不合理值(曾達 362)
// 封頂在實體上限投幣累加不超過此數(溢出的當作進現金箱不計)避免顯示/找零依據出現不可能的數量
public static final int MAX_VIRTUAL_COIN1 = 200;
// 庫存快取變數避免頻繁發送真實串口指令造成 synchronized 卡頓
private int cacheCoin1 = 30;
@ -972,6 +975,11 @@ public class SerialCashAcceptorHandler implements ICashAcceptorHandler {
try {
android.content.SharedPreferences sp = context.getSharedPreferences("MDB_PREFS", Context.MODE_PRIVATE);
virtualCoin1Count = sp.getInt("virtualCoin1Count", 30);
// 舊資料可能已飄超過實體上限( 362)載入時一併夾回上限並回存修正值
if (virtualCoin1Count > MAX_VIRTUAL_COIN1) {
virtualCoin1Count = MAX_VIRTUAL_COIN1;
sp.edit().putInt("virtualCoin1Count", virtualCoin1Count).apply();
}
cacheCoin1 = virtualCoin1Count; // 同步初始化快取
logs.info("[庫存] 初始化1元虛擬庫存自 SharedPreferences: " + virtualCoin1Count);
} catch (Exception e) {
@ -1031,6 +1039,7 @@ public class SerialCashAcceptorHandler implements ICashAcceptorHandler {
if (count1 > 0) {
virtualCoin1Count += count1;
if (virtualCoin1Count > MAX_VIRTUAL_COIN1) virtualCoin1Count = MAX_VIRTUAL_COIN1; // 封頂在實體上限
cacheCoin1 = virtualCoin1Count;
saveVirtualCoin1Count();
for (int i = 0; i < count1; i++) triggerCoinEvent(1);

View File

@ -65,36 +65,10 @@ public class BasicSaleFlow extends SaleFlowHandler {
@Override
public void onBuyRequested() {
// 進支付前先確認主櫃下位機(XinYuan)連線
// 僅當購物清單含主櫃(VMC)商品才檢查失連主動重連仍連不上則擋住不進支付
if (!buyListHasMainCabinetItem()) {
new BuyDialog(context, handlerMain).show();
return;
}
// 直購路徑若剛在點選商品時取貨口預檢成功(有聽到機台正常回報下位機已在線)就跳過這裡
// 重複的連線判斷原因預檢會佔用 XinYuan 串口約 9 緊接著 ensureXinYuanConnected
// 清緩衝送同步包 300ms探詢窗常因送出佇列尚在消化(sendBuffers 塞車)而收不到即時回應
// 誤判未連線並觸發不必要的 shutdown 重連(機台其實一直在回 ACK)預檢已證明在線直接進付款
// BuyDialog 內也會依同一旗標略過取貨口重驗真正無回應(PICKUP_NO_RESPONSE 未記旗標)才走下方連線判斷
if (isFreshPickupPreCheck()) {
new BuyDialog(context, handlerMain).show();
return;
}
// 顯示連線判斷中轉圈提示避免客戶以為當機
final android.app.AlertDialog progress = buildConnectingDialog();
try { progress.show(); } catch (Exception ignore) {}
// ensureXinYuanConnected 內含 Thread.sleep必須在背景執行緒跑
new Thread(() -> {
boolean connected = MyApp.ensureXinYuanConnected();
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
try { if (progress.isShowing()) progress.dismiss(); } catch (Exception ignore) {}
if (!connected) {
showNotConnectedDialog();
return;
}
new BuyDialog(context, handlerMain).show();
});
}).start();
// 下位機連線判斷先移除不穩定常誤報未連線一律直接進付款連線問題交由出貨階段處理
// 取貨口偵測仍保留直購已在點選商品當下檢查(checkPickupOnly)其他入口(購物車/發票重開等)
// BuyDialog.checkPickupPortBeforePay 檢查取貨口(有東西/門才擋無回應則放行)
new BuyDialog(context, handlerMain).show();
}
/** 購物清單是否含主櫃(VMC)商品;非主櫃商品不需檢查主櫃下位機連線。 */
@ -189,19 +163,13 @@ public class BasicSaleFlow extends SaleFlowHandler {
// 單飛已有一個檢查在進行(轉圈提示中)就忽略這次點擊避免疊加查詢把送出佇列塞爆導致失連
if (pickupCheckRunning) return;
pickupCheckRunning = true;
final android.app.AlertDialog progress = buildProgressDialog("下位機連線檢查中......");
final android.app.AlertDialog progress = buildProgressDialog(context.getString(R.string.pickup_checking));
try { progress.show(); } catch (Exception ignore) {}
new Thread(() -> {
com.unibuy.smartdevice.exception.Logs logs = new com.unibuy.smartdevice.exception.Logs(BasicSaleFlow.class);
int result = probePickupHasThing(slotnum);
// 真正無回應(連近 7 秒廣播都沒有下位機確實沒在回)才自動重連下位機後再判斷最多 3
for (int attempt = 1; result == PICKUP_NO_RESPONSE && attempt <= 3; attempt++) {
logs.info("點選商品-下位機無回應,自動重連並重試 (" + attempt + "/3)");
try { MyApp.checkAndReconnectDev(); } catch (Exception ignore) {}
result = probePickupHasThing(slotnum);
}
final int pickup = result;
logs.info("點選商品-連線/取貨口檢查 pickup=" + pickup
// 下位機連線判斷已移除不再自動重連重試只做一次取貨口偵測
final int pickup = probePickupHasThing(slotnum);
logs.info("點選商品-取貨口檢查 pickup=" + pickup
+ " (0=OK,1=有東西,2=門,3=升降梯,4=無通訊,5=在線但狀態未知)");
postMain(() -> {
pickupCheckRunning = false; // 檢查結束復位允許下一次點擊
@ -211,10 +179,10 @@ public class BasicSaleFlow extends SaleFlowHandler {
showPickupBlockedDialog(context.getString(R.string.there_is_something_at_the_pickup_port));
return;
case PICKUP_DOOR:
showPickupBlockedDialog("取貨口門未正常關閉,請先確認門已關好。");
showPickupBlockedDialog(context.getString(R.string.pickup_door_not_closed));
return;
case PICKUP_ELEVATOR:
showPickupBlockedDialog("升降梯未回到原點,請洽服務人員處理。");
showPickupBlockedDialog(context.getString(R.string.pickup_elevator_not_home));
return;
case PICKUP_OK:
// 收到正常(01)= 下位機在線且取貨口空 記旗標
@ -226,12 +194,12 @@ public class BasicSaleFlow extends SaleFlowHandler {
// 下位機在線但此刻沒回報取貨口狀態(忙其他協定3.5 秒內沒等到 VMC_02)
// 選擇保守擋住而非放行寧可多擋也不漏放取貨口有東西請客人稍候再點一次
// 屆時多半已收到取貨口狀態廣播即可正常判定(非未連線故不走重連提示)
showPickupBlockedDialog("正在確認取貨口狀態,請稍候 1~2 秒再點選一次。");
showPickupBlockedDialog(context.getString(R.string.pickup_status_confirming));
break;
default:
// PICKUP_NO_RESPONSE機台連任何 VMC 通訊都沒有2 次重連重試仍無回應 判定真的斷線
// 擋住不開詳情頁連線恢復後客戶重點商品即可
showNotConnectedDialog();
// PICKUP_NO_RESPONSE機台完全無 VMC 通訊連線判斷已移除 不再擋未連線
// 直接放行開詳情頁連線問題交由出貨階段處理BuyDialog 取貨口無回應也放行
onPass.run();
}
});
}).start();
@ -352,6 +320,24 @@ public class BasicSaleFlow extends SaleFlowHandler {
return com.unibuy.smartdevice.ui.dialog.CashPayDialogGeneral.class;
}
/**
* 語言切換時清掉快取的購物車 fragment 並從 FragmentManager 移除舊的它已掛在被取代的舊容器上
* 否則購買後再開購物車會拿到孤兒 fragmentopenFragment isAdded()==true 跳過重加 畫面空白
* 下次 onShoppingCartRequested 會建立新的並掛到新容器
*/
@Override
public void onLocaleSwitched() {
if (cartFragment != null) {
try {
if (context instanceof androidx.fragment.app.FragmentActivity && cartFragment.isAdded()) {
((androidx.fragment.app.FragmentActivity) context).getSupportFragmentManager()
.beginTransaction().remove(cartFragment).commitNowAllowingStateLoss();
}
} catch (Exception ignore) {}
cartFragment = null;
}
}
/** 購物車浮鈕:開啟多品項購物車 Fragment。 */
@Override
public void onShoppingCartRequested() {

View File

@ -944,6 +944,10 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
setBottomNavigation();
startOnSwitchFragment();
// 版面/容器已重建清掉 saleFlowHandler 內快取的購物車 fragment否則購買後再開會空白
if (saleFlowHandler != null) {
saleFlowHandler.onLocaleSwitched();
}
// 重新綁定語言選單入口重繪後 binding 已換新
binding.btnswitchlanEn.setVisibility(View.GONE);
binding.btnswitchlanJa.setVisibility(View.GONE);

View File

@ -79,6 +79,14 @@ public abstract class SaleFlowHandler {
// 預設無動作
}
/**
* 語言切換時呼叫FontendActivity 會重新 inflate 版面/容器saleFlowHandler 內快取的 fragment
* 如購物車會變成掛在舊容器的孤兒 購買後再開會空白子類在此清掉快取並移除舊 fragment
*/
public void onLocaleSwitched() {
// 預設無動作
}
public void onPickupRequested() {
// 預設無動作
}

View File

@ -79,7 +79,7 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter<RecyclerVmcBu
} catch (LogsEmptyException e) {
this.logs.warning(e);
}
holder.binding.textSlot.setText("貨道" + slot.getSlot());
holder.binding.textSlot.setText(context.getString(R.string.slot_label) + slot.getSlot());
// 多國語的判斷統一由 ProductStructure 處理/日未翻譯自動 fallback 中文
holder.binding.textProductName.setText(slot.getProduct().getLocalizedName(context));
@ -96,14 +96,14 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter<RecyclerVmcBu
holder.binding.textPrice.setText("NT$ " + price);
// 綁定庫存數量顯示
holder.binding.textStock.setText("庫存" + count);
holder.binding.textStock.setText(context.getString(R.string.stock_label) + count);
holder.binding.mask.setVisibility(View.GONE);
holder.binding.textMask.setText("");
if (count <= 0) {
holder.binding.mask.setVisibility(View.VISIBLE);
holder.binding.textMask.setText("售完");
holder.binding.textMask.setText(context.getString(R.string.sold_out));
}
// 效期過期顯示暫不販售遮罩並一律禁止購買蓋過售完顯示主要情境是有貨但過效期
@ -114,7 +114,7 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter<RecyclerVmcBu
if (isLock) {
holder.binding.mask.setVisibility(View.VISIBLE);
holder.binding.textMask.setText("暫停販售");
holder.binding.textMask.setText(context.getString(R.string.suspended_sales));
}
if (count > 0 && !isLock && !isExpired) {

View File

@ -338,7 +338,7 @@ public class BuyDialog extends DialogAbstract {
if (isHandled.getAndSet(true)) return;
final String message = doorError
? "取貨口門未正常關閉,請先確認門已關好。"
? BuyDialog.this.getContext().getString(R.string.pickup_door_not_closed)
: BuyDialog.this.getContext().getString(R.string.there_is_something_at_the_pickup_port);
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
@ -459,6 +459,17 @@ public class BuyDialog extends DialogAbstract {
closeDialogCountdown.shutdown();
int flowType = MyApp.getInstance().getReportFlowInfoData().getFlowType();
getLogs().debug("buy flowType:" + flowType);
// 非現金付款(flowType != 9)此刻機台剛進收款模式還在 POLL立即送 inComeByIPC 給足額 credit
// (沿用開機已驗證的 0x08/9999)收款式下位機結束收款後續正常出貨
// 否則機台會卡在等錢(狂送 VMC_29 金錢開關)不送 POLL checkSlot 送不出去 出貨失敗
// 現金(9)不送由現金流程依實收金額自行送 inComeByIPC避免預付 credit 造成免費出貨
if (flowType != 9) {
try {
MyApp.getInstance().getDevXinYuanController().addSendBuffer(
MyApp.getInstance().getDevXinYuanController().getDevXinYuan().inComeByIPC((byte) 0x08, 9999));
getLogs().info("非現金付款(flowType=" + flowType + ") → 送 inComeByIPC 給下位機收款 credit");
} catch (Exception ignore) {}
}
// if (flowType == 5 || flowType == 7 || flowType == 30) {
if (flowType == 5 || flowType == 7) {
changeCheckout();

View File

@ -352,7 +352,7 @@ public class CheckoutAccessCodeDialog extends DialogAbstract {
String number = binding.editTextNumber.getText().toString().trim();
// 長度檢查
if (number.isEmpty() || number.length() < 8) {
getHandlerMain().start(getClass().getSimpleName(), "通行碼長度不能少於 8 碼");
getHandlerMain().start(getClass().getSimpleName(), getCtx().getString(R.string.passcode_min_8));
return;
}
submitPassCode(number);
@ -369,11 +369,11 @@ public class CheckoutAccessCodeDialog extends DialogAbstract {
// 防護通行碼從屬取貨模組任一關閉即不開放呼叫 B670即使 UI 漏出入口也擋下
if (!(MyApp.getInstance().getDevSet().isPickupModule() && MyApp.getInstance().getDevSet().isPassCode())) {
openBarcode = true;
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "通行碼功能未開放");
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), getCtx().getString(R.string.passcode_not_enabled));
return;
}
// 送出前顯示驗證中避免使用者以為沒反應
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "驗證中…");
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), getCtx().getString(R.string.verifying));
new com.unibuy.smartdevice.external.StarCloudAPI(getHandlerMain(), getHandlerMain())
.verifyPassCode(code, new com.unibuy.smartdevice.external.StarCloudAPI.VerificationCallback() {
@Override

View File

@ -637,7 +637,7 @@ public class CheckoutCashDialog extends DialogAbstract {
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("取消("+countdown+")");
binding.buttonCancel.setText(getCtx().getString(R.string.cancel)+"("+countdown+")");
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {

View File

@ -327,7 +327,7 @@ public class CheckoutFreeGiftCodeDialog extends DialogAbstract {
public void changeOk() {
String number = binding.editTextNumber.getText().toString().trim();
if (number.isEmpty() || number.length() < 8) {
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "來店禮代碼需 8 碼");
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), getCtx().getString(R.string.gift_code_min_8));
return;
}
submitWelcomeGift(number);
@ -341,10 +341,10 @@ public class CheckoutFreeGiftCodeDialog extends DialogAbstract {
// 防護來店禮關閉即不開放呼叫 B690即使 UI 漏出入口也擋下
if (!MyApp.getInstance().getDevSet().isWelcomeGift()) {
openBarcode = true;
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "來店禮功能未開放");
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), getCtx().getString(R.string.gift_not_enabled));
return;
}
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "驗證中…");
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), getCtx().getString(R.string.verifying));
new com.unibuy.smartdevice.external.StarCloudAPI(getHandlerMain(), getHandlerMain())
.verifyWelcomeGift(code, new com.unibuy.smartdevice.external.StarCloudAPI.WelcomeGiftCallback() {
@Override
@ -364,7 +364,7 @@ public class CheckoutFreeGiftCodeDialog extends DialogAbstract {
.setDiscountPercentage((100 - discountValue) / 100f);
} else {
openBarcode = true;
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "不支援的折扣類型");
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), getContext().getString(R.string.unsupported_discount_type));
return;
}
getHandlerMain().start(getClass().getSimpleName(), Option.CHANGE_BUY.getOption(), "change buy");

View File

@ -1,4 +1,5 @@
package com.unibuy.smartdevice.ui.dialog;
import com.unibuy.smartdevice.R;
import android.content.Context;
import android.graphics.Color;
@ -209,7 +210,7 @@ public class CheckoutNfcCardDialog extends DialogAbstract {
closeDialogCountdown.start(30);
binding.textPrice.setText(String.valueOf(getTotalPrice()));
binding.textStatus.setText("交易讀取中,請將卡片/手機停留片刻,\n勿快速移動系統完成後會自動返回。");
binding.textStatus.setText(getCtx().getString(R.string.reading_transaction));
ProductStructure buyProduct = MyApp.getInstance().getBuyList().get(0).getProduct();
MyApp.getInstance().getReportFlowInfoData().setProductId(buyProduct.getProductID());
@ -259,7 +260,7 @@ public class CheckoutNfcCardDialog extends DialogAbstract {
nfcPayController.start(1, getTotalPrice());
} catch (LogsIOException e) {
getLogs().warning(e);
binding.textStatus.setText("刷卡機無法開啟");
binding.textStatus.setText(getCtx().getString(R.string.card_reader_cannot_open));
getHandlerMain().getHandler().postDelayed(new Runnable() {
@Override
public void run() {
@ -348,7 +349,7 @@ public class CheckoutNfcCardDialog extends DialogAbstract {
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("取消("+countdown+")");
binding.buttonCancel.setText(getCtx().getString(R.string.cancel)+"("+countdown+")");
}
public class CloseDialogOnThreadHandler extends HandlerMainCountdown {

View File

@ -1,4 +1,5 @@
package com.unibuy.smartdevice.ui.dialog;
import com.unibuy.smartdevice.R;
import android.content.Context;
import android.graphics.Color;
@ -213,7 +214,7 @@ public class CheckoutNfcPhoneDialog extends DialogAbstract {
binding.textPrice.setText(String.valueOf(getTotalPrice()));
binding.textStatus.setText("交易讀取中,請將卡片/手機停留片刻,\n勿快速移動系統完成後會自動返回。");
binding.textStatus.setText(getCtx().getString(R.string.reading_transaction));
ProductStructure buyProduct = MyApp.getInstance().getBuyList().get(0).getProduct();
MyApp.getInstance().getReportFlowInfoData().setProductId(buyProduct.getProductID());
@ -263,7 +264,7 @@ public class CheckoutNfcPhoneDialog extends DialogAbstract {
nfcPayController.start(4, getTotalPrice());
} catch (LogsIOException e) {
getLogs().warning(e);
binding.textStatus.setText("刷卡機無法開啟");
binding.textStatus.setText(getCtx().getString(R.string.card_reader_cannot_open));
getHandlerMain().getHandler().postDelayed(new Runnable() {
@Override
public void run() {
@ -352,7 +353,7 @@ public class CheckoutNfcPhoneDialog extends DialogAbstract {
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("取消("+countdown+")");
binding.buttonCancel.setText(getCtx().getString(R.string.cancel)+"("+countdown+")");
}
public class CloseDialogOnThreadHandler extends HandlerMainCountdown {

View File

@ -1,4 +1,5 @@
package com.unibuy.smartdevice.ui.dialog;
import com.unibuy.smartdevice.R;
import android.content.Context;
import android.graphics.Color;
@ -210,7 +211,7 @@ public class CheckoutNfcRechargeDialog extends DialogAbstract {
binding.textPrice.setText(String.valueOf(getTotalPrice()));
binding.textStatus.setText("交易讀取中,請將卡片/手機停留片刻,\n勿快速移動系統完成後會自動返回。");
binding.textStatus.setText(getCtx().getString(R.string.reading_transaction));
ProductStructure buyProduct = MyApp.getInstance().getBuyList().get(0).getProduct();
MyApp.getInstance().getReportFlowInfoData().setProductId(buyProduct.getProductID());
@ -260,7 +261,7 @@ public class CheckoutNfcRechargeDialog extends DialogAbstract {
nfcPayController.start(2, getTotalPrice());
} catch (LogsIOException e) {
getLogs().warning(e);
binding.textStatus.setText("刷卡機無法開啟");
binding.textStatus.setText(getCtx().getString(R.string.card_reader_cannot_open));
getHandlerMain().getHandler().postDelayed(new Runnable() {
@Override
public void run() {
@ -349,7 +350,7 @@ public class CheckoutNfcRechargeDialog extends DialogAbstract {
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("取消("+countdown+")");
binding.buttonCancel.setText(getCtx().getString(R.string.cancel)+"("+countdown+")");
}
public class CloseDialogOnThreadHandler extends HandlerMainCountdown {

View File

@ -594,7 +594,7 @@ public class CheckoutTapPayDialog extends DialogAbstract {
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("取消("+countdown+")");
binding.buttonCancel.setText(getCtx().getString(R.string.cancel)+"("+countdown+")");
}
public class ReadBarCodeOnScheduler extends HandlerMainScheduler {

View File

@ -99,8 +99,8 @@ public class DialogProductDetail extends DialogAbstract {
// 商品名稱多語貨道庫存
binding.textProductName.setText(slot.getProduct().getLocalizedName(context));
binding.slotNumber.setText("貨道" + slot.getSlot());
binding.inventoryNumber.setText("庫存" + slot.getCount());
binding.slotNumber.setText(context.getString(R.string.slot_label) + slot.getSlot());
binding.inventoryNumber.setText(context.getString(R.string.stock_label) + slot.getCount());
// 商品規格描述多語純文字含換行後台空白則隱藏整塊
// setText 原樣呈現換行與空格縮排版面已設 gravity=start故縮排不會被置中打亂

View File

@ -465,8 +465,8 @@ public class DispatchDialog extends DialogAbstract {
} catch (Exception e) {
getLogs().warning(e);
}
// 找不到替換檔或解碼失敗 用內建預設圖此版專給代樂美預設為 qrcode_msg_dailemei
binding.imgDeliveryFailed.setImageResource(R.drawable.qrcode_msg_dailemei);
// 找不到替換檔或解碼失敗 用內建預設圖
binding.imgDeliveryFailed.setImageResource(R.drawable.qrcode_msg);
}
/** 依螢幕尺寸做降採樣解碼,避免大圖 OOM。 */
@ -490,11 +490,11 @@ public class DispatchDialog extends DialogAbstract {
countDownTimer = new CountDownTimer(15_000, 1_000) { // 10 每秒 tick 一次
public void onTick(long millisUntilFinished) {
int seconds = (int) (millisUntilFinished / 1000);
tvCountdown.setText("" + seconds + "s後關閉");
tvCountdown.setText(getCtx().getString(R.string.close_after_seconds, seconds));
}
public void onFinish() {
tvCountdown.setText("0s後關閉");
tvCountdown.setText(getCtx().getString(R.string.close_after_seconds, 0));
}
}.start();
}
@ -540,9 +540,35 @@ public class DispatchDialog extends DialogAbstract {
cancel();
}
// 有待機畫面機台出貨完成後停留幾秒取貨提示再跳待機畫面可調
private static final int STANDBY_PICKUP_SECONDS = 5;
/**
* 出貨完成後跳待機畫面僅有待機的機台 !AppEntry.isCmuh
* 交易已於出貨成功處 publishFinalize 結案此處收掉本對話框計時器並導向 AppEntry.initialActivity()
* 有待機機台即 HomeActivity取貨門已開跳待機不影響客人取貨只是縮短等待感
*/
private void goToStandby() {
try { closeDialogCountdown.shutdown(); } catch (Exception ignore) {}
try { if (countDownTimer != null) countDownTimer.cancel(); } catch (Exception ignore) {}
MyApp.getInstance().getBuyList().clear();
try {
Intent intent = new Intent(getCtx(), com.unibuy.smartdevice.AppEntry.initialActivity());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
getCtx().startActivity(intent);
} catch (Exception e) {
getLogs().warning(e);
}
cancel(); // 關閉本對話框
}
private boolean isDispatchingNext = false;
private boolean isCheckSlotByVMC = false;
private int checkSlotRunCount = 0;
// Cmuh 出貨遇下位機未連線做一輪重連重新出貨的計數(限一輪避免無限迴圈)
// 機台一旦有回應(isCMD)即歸零讓後續若再度失聯能再救一輪
private int dispatchReconnectRound = 0;
public class DevCheckSlotByVMC extends DevController.ReadBufferOnScheduler {
private int slot;
@ -594,9 +620,10 @@ public class DispatchDialog extends DialogAbstract {
getLogs().info("vmc isCMD:" + isCMD + " isStatus:" + isStatus);
if (isCMD) {
dispatchReconnectRound = 0; // 機台有回應已連線重置重連輪數
if (isStatus) {
getLogs().info("-----------------商品出貨中-----------------");
setTextViewCountdown("商品出貨中", 65);
setTextViewCountdown(getCtx().getString(R.string.dispensing_product), 65);
MyApp.getInstance().getDevXinYuanController()
.getReadBufferByNow(new DevGetProductByVMC(handlerMain, slot), 5000);
MyApp.getInstance().getDevXinYuanController().addSendBuffer(
@ -612,6 +639,7 @@ public class DispatchDialog extends DialogAbstract {
}
} else {
boolean isCmuh = com.unibuy.smartdevice.AppEntry.isCmuh();
// Cmuh 重試 3 次仍無回應就進入下方重連重新出貨那一輪(give-up 分支)早點救援較快
int maxRetries = isCmuh ? 10 : 3;
if (checkSlotRunCount >= maxRetries) {
buttonControlCountdown.shutdown();
@ -623,12 +651,29 @@ public class DispatchDialog extends DialogAbstract {
}
getSrcHandlerMain().start(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(),
"領藥有問題 請等3秒後再重掃");
checkSlotRunCount = 0;
} else if (dispatchReconnectRound < 1) {
// Cmuh重試用盡仍下位機未連線 重連救活控制器後重新出貨一輪(限一輪避免無限迴圈)
dispatchReconnectRound++;
getLogs().info("下位機未連線 → 重連後重新出貨 (第 " + dispatchReconnectRound + " 輪)");
try { MyApp.checkAndReconnectDev(); } catch (Exception ignore) {}
checkSlotRunCount = 0;
isCheckSlotByVMC = false;
// 給重連後的控制器時間重開串口/重啟讀取迴圈再重跑一次出貨
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
if (!isFinalizing) {
try { currentlyProcessingGoods(); } catch (Exception e) { getLogs().warning(e); }
}
}, 2500);
} else {
// 已重連重試一輪仍未回應 真的失敗
getLogs().info("重連後下位機仍未回應,出貨作業中止");
getSrcHandlerMain().start(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(),
getCtx().getString(R.string.slave_computer_did_not_respond_wait_3_seconds_try_again));
MyApp.getInstance().getDevXinYuanController().start();
checkSlotRunCount = 0;
dispatchReconnectRound = 0;
}
checkSlotRunCount = 0;
} else {
getLogs().info("機台未回應,第 " + (checkSlotRunCount + 1) + " 次重試 checkSlot..."
+ (isCmuh ? " (溫和模式,不發送指令)" : ""));
@ -814,7 +859,7 @@ public class DispatchDialog extends DialogAbstract {
if (isShipped) { // 正在要出貨的話
if (count < quantity) { // count 目前出了多少貨 Quantity 總貨物數量
// 設定商品名稱和圖片
binding.textProductName.setText(product.getProductName());
binding.textProductName.setText(product.getLocalizedName(getContext()));
try {
new ImageGlide(getContext()).fileload(product.getProductImg(), binding.imageProductPicture);
@ -1033,7 +1078,7 @@ public class DispatchDialog extends DialogAbstract {
if (com.unibuy.smartdevice.AppEntry.isCmuh()) {
insertDrgnoToMemo();
}
publishFinalizeUnlessPassCode();
publishFinalize();
getSrcHandlerMain().send(getClass().getSimpleName(), FontendActivity.Option.RESET_DATA.getOption(),
"update data");
saveData();
@ -1061,24 +1106,33 @@ public class DispatchDialog extends DialogAbstract {
binding.buttonOk.setTextColor(Color.BLACK);
setButtonOkText(0); // 顯示取貨確認不帶倒數
// 顯示取貨倒數 15 秒後自動關閉對話框
// 顯示取貨倒數後自動結束
// - 有待機畫面的機台(!isCmuh)縮短取貨提示秒數(STANDBY_PICKUP_SECONDS)跳待機畫面
// 減少客人等待感取貨門已開跳待機不影響取貨
// - 無待機(中國醫/台大北護等 pickup_sheet=isCmuh)維持原本 15 關閉回原流程
isChangeStatus = true;
// 💡 統一都有括號消除初始與倒數的格式不一致閃爍
final boolean hasStandby = !com.unibuy.smartdevice.AppEntry.isCmuh();
final int pickupSeconds = hasStandby ? STANDBY_PICKUP_SECONDS : 15;
setTextViewCountdown(
getCtx().getString(R.string.pickup_port_open) + "(15)"
getCtx().getString(R.string.pickup_port_open) + "(" + pickupSeconds + ")"
+ getCtx().getString(R.string.pick_up_within),
65);
countDownTimer = new CountDownTimer(15_000, 1_000) {
countDownTimer = new CountDownTimer(pickupSeconds * 1000L, 1_000) {
public void onTick(long millisUntilFinished) {
int seconds = (int) (millisUntilFinished / 1000);
setTextViewCountdown(seconds);
}
public void onFinish() {
dialogCancel();
if (hasStandby) {
goToStandby();
} else {
dialogCancel();
}
}
}.start();
getLogs().info("📢 出貨完畢,取貨倒數 15 秒後關閉對話框");
getLogs().info("📢 出貨完畢,取貨倒數 " + pickupSeconds + " 秒後"
+ (hasStandby ? "跳待機畫面" : "關閉對話框"));
isDispatchingNext = false;
isGetProductByVMC = false;
@ -1149,21 +1203,20 @@ public class DispatchDialog extends DialogAbstract {
}
getLogs().info("出貨失敗,發送 MQTT Finalize 結案訊息: " + message);
publishFinalizeUnlessPassCode();
publishFinalize();
getSrcHandlerMain().send(getClass().getSimpleName(), FontendActivity.Option.RESET_DATA.getOption(),
"update data");
saveData();
}
/**
* 發送 MQTT 交易結案 flowType 5通行碼為工程師/內部硬體測試不上報交易
* B670 驗證已於後台記 log避免污染銷售/營收且不扣減雲端庫存
* 發送 MQTT 交易結案
* 通行碼(flowType 5)現在也一併上報依需求完整交易進銷售紀錄與一般銷售相同處理
* 會建立訂單記出貨明細/貨道扣雲端庫存計入交易筆數單次通行碼在 B670 驗證時已消耗
* 後台結案 case 5 只記 log不會雙扣
*/
private void publishFinalizeUnlessPassCode() {
// finalizeBuilder 在進入出貨當下擷取的快照判斷而非結案當下的全域即時值
// 防止出貨中途全域 flowType 被洗回 0如殘留加購倒數彈出 BuyDialog而導致通行碼被誤報
if (finalizeBuilder != null && finalizeBuilder.getOrderFlowType() == 5) {
getLogs().info("通行碼(payment_type=5) 為內部測試出貨,略過 MQTT 交易結案上報。");
private void publishFinalize() {
if (finalizeBuilder == null) {
return;
}
MqttManager.getInstance().publishTransactionFinalize(finalizeBuilder.build());
@ -1236,12 +1289,26 @@ public class DispatchDialog extends DialogAbstract {
buttonControlCountdown.setCountdown(25);
}
getLogs().info("11111");
MyApp.getInstance().getDevXinYuanController().getReadBufferByNow(new DevCheckSlotByVMC(getHandlerMain(), slot));
getLogs().info("22222");
MyApp.getInstance().getDevXinYuanController()
.addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot));
getLogs().info("33333");
// 出貨前最後一道保險確認下位機連線失連就先自動重連並等到活著再送出貨指令
// 這是百分百在出貨當下遇失連可自動補出貨的關鍵ensure 內含 ping/重連的 Thread.sleep
// 必須背景執行完成後回主執行緒才送 checkSlot(檢查放這裡而非按下直接購買時因為付款到
// 出貨可能隔數十秒當時連著不代表出貨時還連著下位機常在重啟後短暫失聯)
setTextViewCountdown(getCtx().getString(R.string.device_initializing), 65);
new Thread(() -> {
boolean alive = MyApp.ensureXinYuanConnectedForDispatch(8000);
getLogs().info("出貨前確認下位機連線結果: " + alive);
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
if (isFinalizing)
return;
getLogs().info("11111");
MyApp.getInstance().getDevXinYuanController()
.getReadBufferByNow(new DevCheckSlotByVMC(getHandlerMain(), slot));
getLogs().info("22222");
MyApp.getInstance().getDevXinYuanController()
.addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot));
getLogs().info("33333");
});
}).start();
}
private void devRunElectric(int field, int slot, int increase)

View File

@ -186,6 +186,14 @@ public class FontendPickupCodeDialog extends DialogAbstract {
MyApp.getInstance().initialReportData();
// 防呆取貨碼對話框開啟時先清空 BuyList避免之前殘留未清掉的購物車品項
// 被取貨碼流程誤當取貨碼 0 元出貨只有本對話框掃碼驗證成功回傳的品項才可進 BuyList 出貨
if (MyApp.getInstance().getBuyList() != null && MyApp.getInstance().getBuyList().size() > 0) {
getLogs().info("開啟取貨碼對話框時偵測到殘留 BuyList " + MyApp.getInstance().getBuyList().size()
+ " 筆,已清空以防誤出貨(避免取消鍵白拿/假取貨碼紀錄)");
MyApp.getInstance().getBuyList().clear();
}
setInputMode(true);
binding.btnModeKeypad.setOnClickListener(v -> {
@ -203,7 +211,9 @@ public class FontendPickupCodeDialog extends DialogAbstract {
httpAPI = new HttpAPI(getHandlerMain());
binding.buttonFinish.setOnClickListener(v -> changeOk());
binding.buttonCancel.setOnClickListener(v -> changeBuy());
// 修正取消鍵只關閉對話框並清空 BuyList絕不可呼叫 changeBuy() 出貨
// 原本寫成 changeBuy()BuyList 若有殘留品項按取消會被 0 元出貨還誤記成取貨碼(payment_type=6)
binding.buttonCancel.setOnClickListener(v -> dialogCancel());
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
closeDialogCountdown.start(40);
@ -398,7 +408,7 @@ public class FontendPickupCodeDialog extends DialogAbstract {
}
// 長度檢查
if (number.isEmpty() || number.length() < 8) {
getHandlerMain().start(getClass().getSimpleName(), "取貨碼長度不能少於 8 碼");
getHandlerMain().start(getClass().getSimpleName(), getCtx().getString(R.string.pickup_code_min_8));
return;
}
submitPickupCode(number);
@ -426,11 +436,11 @@ public class FontendPickupCodeDialog extends DialogAbstract {
// 防護取貨碼從屬取貨模組任一關閉即不開放呼叫 B660即使 UI 漏出入口也擋下
if (!(MyApp.getInstance().getDevSet().isPickupModule() && MyApp.getInstance().getDevSet().isPickupCode())) {
openBarcode = true;
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "取貨碼功能未開放");
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), getCtx().getString(R.string.pickup_code_not_enabled));
return;
}
// 送出前顯示驗證中避免使用者以為沒反應
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "驗證中…");
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), getCtx().getString(R.string.verifying));
new com.unibuy.smartdevice.external.StarCloudAPI(getHandlerMain(), getHandlerMain())
.verifyPickupCode(code, new com.unibuy.smartdevice.external.StarCloudAPI.PickupVerificationCallback() {
@Override
@ -513,7 +523,7 @@ public class FontendPickupCodeDialog extends DialogAbstract {
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("取消 " + countdown + "");
binding.buttonCancel.setText(getContext().getString(R.string.cancel_seconds_format, countdown));
}
public void setStatusText(String message) {
@ -671,12 +681,12 @@ public class FontendPickupCodeDialog extends DialogAbstract {
if (isHandled.getAndSet(true)) return;
// 取貨口門未關閉提示使用者並阻止後續購買流程
handlerMain.start("FontendPickupCodeDialog", "取貨口門未正常關閉,請先確認門已關好");
handlerMain.start("FontendPickupCodeDialog", getContext().getString(R.string.pickup_door_not_closed));
// 使用主執行緒顯示 AlertDialog
new Handler(Looper.getMainLooper()).post(() -> {
AlertDialog dialog = new AlertDialog.Builder(getContext())
.setTitle(R.string.abnormal_reminder)
.setMessage("取貨口門未正常關閉,請先確認門已關好。")
.setMessage(R.string.pickup_door_not_closed)
.setCancelable(false)
.setPositiveButton(R.string.i_knew, (d, which) -> {
d.dismiss();

View File

@ -333,14 +333,14 @@ public class InvoiceCustomerIdentifierDialog extends DialogAbstract {
public void changeNextStep() {
String number = binding.editTextNumber.getText().toString();
if (number.length() < 8) {
getHandlerMain().start(getClass().getSimpleName(), "統一編號長度不能少於 8 碼");
getHandlerMain().start(getClass().getSimpleName(), getCtx().getString(R.string.tax_id_min_8));
return;
}
binding.linearStep1.setVisibility(View.GONE);
binding.linearStep2.setVisibility(View.VISIBLE);
binding.buttonNextStep.setVisibility(View.GONE);
binding.buttonFinish.setVisibility(View.VISIBLE);
binding.textInvoiceCarrier.setText("輸入電話號碼");
binding.textInvoiceCarrier.setText(getCtx().getString(R.string.enter_phone_number));
closeDialogCountdown.setCountdown(40);
}
@ -351,12 +351,12 @@ public class InvoiceCustomerIdentifierDialog extends DialogAbstract {
// 長度檢查
if (number.length() < 8) {
getHandlerMain().start(getClass().getSimpleName(), "統一編號長度不能少於 8 碼");
getHandlerMain().start(getClass().getSimpleName(), getCtx().getString(R.string.tax_id_min_8));
return;
}
if (phone.length() < 10) {
getHandlerMain().start(getClass().getSimpleName(), "電話號碼長度不能少於 10 碼");
getHandlerMain().start(getClass().getSimpleName(), getCtx().getString(R.string.phone_min_10));
return;
}
MyApp.getInstance().getInvoiceData().setCustomerIdentifier(number);

View File

@ -244,14 +244,14 @@ public class InvoiceDonationDialog extends DialogAbstract {
public void changeOk() {
int selectedPosition = donationAdapter.getSelectedPosition();
if (selectedPosition < 0) {
Toast.makeText(getCtx(), "請選擇捐贈機構", Toast.LENGTH_SHORT).show();
Toast.makeText(getCtx(), getCtx().getString(R.string.select_charity), Toast.LENGTH_SHORT).show();
return;
}
String selectedName = organizationNames.get(selectedPosition);
String loveCode = donationMap.get(selectedName);
MyApp.getInstance().getInvoiceData().setLoveCode(loveCode);
MyApp.getInstance().getReportFlowInfoData().setInvoiceInfo("loveCode:" + loveCode);
Toast.makeText(getCtx(), "選擇" + selectedName, Toast.LENGTH_SHORT).show();
Toast.makeText(getCtx(), getCtx().getString(R.string.choose) + "" + selectedName, Toast.LENGTH_SHORT).show();
changeCheckout();
}
@ -302,7 +302,7 @@ public class InvoiceDonationDialog extends DialogAbstract {
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("返回發票選擇(" + countdown + ")");
binding.buttonCancel.setText(getCtx().getString(R.string.back_to_invoice) + "(" + countdown + ")");
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {

View File

@ -4,6 +4,7 @@ import android.content.Context;
import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.databinding.DialogInvoiceTypeBinding;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.HandlerMainCountdown;
@ -146,7 +147,7 @@ public class InvoiceTypeDialog extends DialogAbstract {
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("返回" + countdown + "");
binding.buttonCancel.setText(getCtx().getString(R.string.return_to) + countdown + getCtx().getString(R.string.second));
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {

View File

@ -1,4 +1,5 @@
package com.unibuy.smartdevice.ui.dialog;
import com.unibuy.smartdevice.R;
import android.content.Context;
import android.os.Handler;
@ -185,7 +186,7 @@ public class MemberCardPickupDialog extends DialogAbstract {
private void handleCardDetected(String cardNo) {
if (!cardNo.isEmpty()) {
getLogs().info("MemberCardPickupDialog - Card Detected: " + cardNo);
binding.editCardNo.setHint("正在驗證卡片...");
binding.editCardNo.setHint(getContext().getString(R.string.verifying_card));
binding.editCardNo.setText("");
// 呼叫 B680 遠端驗證
@ -201,13 +202,13 @@ public class MemberCardPickupDialog extends DialogAbstract {
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(), "商品已售罄,請重新選擇");
getHandlerMain().start(getClass().getSimpleName(), Option.TOAST.getOption(), getCtx().getString(R.string.product_sold_out_reselect));
dialogCancel(); // 內部已清空 BuyList + dismiss
return;
}
if (liveSlot != null && liveSlot.isExpired()) {
getLogs().info("商品已過效期 slot=" + buy.getSlot() + ",取消出貨,返回銷售平台。");
getHandlerMain().start(getClass().getSimpleName(), Option.TOAST.getOption(), "商品已過效期,暫不販售");
getHandlerMain().start(getClass().getSimpleName(), Option.TOAST.getOption(), getContext().getString(R.string.product_expired_not_for_sale));
dialogCancel(); // 內部已清空 BuyList + dismiss
return;
}
@ -266,7 +267,7 @@ public class MemberCardPickupDialog extends DialogAbstract {
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("取消(" + countdown + ")");
binding.buttonCancel.setText(getCtx().getString(R.string.cancel)+"(" + countdown + ")");
}
public class CloseDialogOnThreadHandler extends HandlerMainCountdown {

View File

@ -1,4 +1,5 @@
package com.unibuy.smartdevice.ui.dialog;
import com.unibuy.smartdevice.R;
import android.content.Context;
@ -86,7 +87,7 @@ public class PickupSuccessDialog extends DialogAbstract {
this.laneNumber = laneNumber;
if (binding != null) {
binding.tvLaneNumber.setText(laneNumber);
binding.tvSubtitle.setText("請至" + laneNumber + "號櫃門拿取您的商品");
binding.tvSubtitle.setText(getCtx().getString(R.string.go_to_cabinet_format, laneNumber));
}
}
@ -117,7 +118,7 @@ public class PickupSuccessDialog extends DialogAbstract {
}
public void setButtonConfirmText(long countdown) {
binding.btnConfirm.setText("確認已取貨(" + countdown + "秒)");
binding.btnConfirm.setText(getCtx().getString(R.string.confirm_taken_format, countdown));
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {

View File

@ -60,7 +60,7 @@ public class RecyclerDialogDispatchListAdpter extends RecyclerView.Adapter<Recyc
String productID = dispatchProduct.getProduct().getProductID();
if (!productID.isEmpty() && !productID.equals("?")) {
holder.binding.textProductName.setText(dispatchProduct.getProduct().getProductName());
holder.binding.textProductName.setText(dispatchProduct.getProduct().getLocalizedName(context));
} else {
holder.binding.textProductName.setText(context.getString(R.string.click_select_product));//點我選擇商品
}
@ -84,7 +84,7 @@ public class RecyclerDialogDispatchListAdpter extends RecyclerView.Adapter<Recyc
int price = machinePrice > 0? machinePrice: sellingPrice;
holder.binding.textCount.setText(String.format("已出貨數量(%d / %d", count, quantity));
holder.binding.textCount.setText(context.getString(R.string.shipped_quantity_format, count, quantity));
logs.info("isShipped:" + dispatchProduct.isShipped());
@ -95,12 +95,12 @@ public class RecyclerDialogDispatchListAdpter extends RecyclerView.Adapter<Recyc
} else if (dispatchProduct.isShipped()) {
try {
if (SlotField.getSlotField(dispatchProduct.getField()).isCanOneShipped()) {
holder.binding.textDispatchStatus.setText(context.getString(R.string.shipping)+""+(count+1)+"件)");//出貨中
holder.binding.textDispatchStatus.setText(context.getString(R.string.shipping_count_format, count + 1));//出貨中
} else {
holder.binding.textDispatchStatus.setText(context.getString(R.string.shipping)+"(All)");//出貨中
holder.binding.textDispatchStatus.setText(context.getString(R.string.shipping_all));//出貨中
}
} catch (LogsEmptyException e) {
holder.binding.textDispatchStatus.setText(context.getString(R.string.shipping)+""+(count+1)+"件)");//出貨中
holder.binding.textDispatchStatus.setText(context.getString(R.string.shipping_count_format, count + 1));//出貨中
}
holder.binding.textDispatchStatus.setTextColor(ContextCompat.getColor(context, R.color.onPrimary));

View File

@ -63,7 +63,7 @@ public class RecyclerDialogPickupCodeBuyListAdpter extends RecyclerView.Adapter<
holder.binding.textPosition.setText(String.valueOf(position));
String productID = buyProduct.getProduct().getProductID();
if (!productID.isEmpty() && !productID.equals("?")) {
holder.binding.textProductName.setText(buyProduct.getProduct().getProductName());
holder.binding.textProductName.setText(buyProduct.getProduct().getLocalizedName(context));
} else {
holder.binding.textProductName.setText(context.getString(R.string.click_select_product));//
}
@ -84,7 +84,7 @@ public class RecyclerDialogPickupCodeBuyListAdpter extends RecyclerView.Adapter<
int price = machinePrice > 0? machinePrice: sellingPrice;
holder.binding.textPrice.setText("$"+price);
holder.binding.textCount.setText("出貨數量:" + count + "");
holder.binding.textCount.setText(context.getString(R.string.dispatch_quantity_format, count));
logs.info("field:" + buyProduct.getField());
}

View File

@ -510,6 +510,40 @@ public class CashMachineSettings extends AppCompatActivity implements View.OnCli
// 將收到的原始事件印到畫面 Debug 取代原本每則跳 Toast 的干擾
appendDebugLog("[事件] " + event);
if (event == null) return;
// MDB 轉換盒(SerialCashAcceptorHandler)的十進位格式優先處理
// 舊的 19 hex 格式每段限 2 (0~255)當某面額數量>255( 1元虛擬庫存) %02X 會變 3
// 使整包被每段須 2 字元驗證丟棄四種硬幣全部顯示不出來改吃這個十進位格式即無此限制
if (event.startsWith("COIN_STOCK:")) {
try {
String[] v = event.substring("COIN_STOCK:".length()).split(",");
if (v.length >= 4) {
coin1_count = Integer.parseInt(v[0].trim());
coin5_count = Integer.parseInt(v[1].trim());
coin10_count = Integer.parseInt(v[2].trim());
coin50_count = Integer.parseInt(v[3].trim());
tvCoin50Count.setText("50元數量:" + coin50_count);
tvCoin10Count.setText("10元數量:" + coin10_count);
tvCoin5Count.setText("5元數量:" + coin5_count);
tvCoin1Count.setText("1元數量:" + coin1_count);
}
} catch (NumberFormatException e) {
Log.e("MDB_EVENT", "COIN_STOCK 解析失敗: " + e.getMessage());
}
return;
}
if (event.startsWith("NOTE_STOCK:")) {
try {
note100_count = Integer.parseInt(event.substring("NOTE_STOCK:".length()).trim());
tvBillStatusDisplay.setText("循環區裡面百鈔數量" + note100_count);
} catch (NumberFormatException e) {
Log.e("MDB_EVENT", "NOTE_STOCK 解析失敗: " + e.getMessage());
}
return;
}
if (event.length() >= 5) {
String code = event.substring(0, 5);
switch (code) {

View File

@ -36,7 +36,7 @@ class CartOperationAdapter(layoutResId: Int, data: MutableList<SlotStructure>) :
holder.setText(R.id.textProductName, name)
.setText(R.id.totalPriceTextView, "NT$ ${result.currentItemPrice}")
.setText(R.id.textStock, String.format("庫存%02d件", item.count))
.setText(R.id.textStock, ctx.getString(R.string.stock_pieces_format, item.count))
.setText(R.id.textQuantity, "${result.currentItemCount}")
.setText(R.id.textPrice, "NT$ ${item.product.realPrice}")

View File

@ -385,7 +385,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="取貨碼"
android:text="@string/pickup_code"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"

View File

@ -83,12 +83,12 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/btnModeKeypad"
style="@style/Widget.App.InputModeButton"
android:text="鍵盤輸入" />
android:text="@string/keyboard_input" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnModeScan"
style="@style/Widget.App.InputModeButton"
android:text="掃描條碼" />
android:text="@string/scan_barcode" />
</LinearLayout>
<LinearLayout

View File

@ -12,7 +12,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="請選擇支付方式"
android:text="@string/select_payment_method"
android:textSize="30sp"
android:textColor="@color/primary"
android:padding="10dp"
@ -122,7 +122,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:gravity="center"
android:text="信用卡"
android:text="@string/credit_card"
android:textColor="@color/black"
android:textSize="25sp" />
<ImageView
@ -155,7 +155,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:gravity="center"
android:text="電子票證"
android:text="@string/e_ticket"
android:textColor="@color/black"
android:textSize="25sp"/>
<ImageView
@ -188,7 +188,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:gravity="center"
android:text="手機NFC"
android:text="@string/phone_nfc"
android:textColor="@color/black"
android:textSize="25sp" />
<ImageView
@ -223,7 +223,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:gravity="center"
android:text="掃碼支付-玉山"
android:text="@string/qr_pay_esun"
android:textColor="@color/black"
android:textSize="25sp" />
<ImageView
@ -256,7 +256,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:gravity="center"
android:text="掃碼支付-TapPay"
android:text="@string/qr_pay_tappay"
android:textColor="@color/black"
android:textSize="25sp" />
<ImageView
@ -289,7 +289,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center"
android:text="現金支付"
android:text="@string/cash_payment"
android:textColor="@color/black"
android:textSize="25sp"/>
<ImageView
@ -322,7 +322,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Line Pay 支付"
android:text="@string/line_pay"
android:textColor="@color/black"
android:textSize="25sp" />
</LinearLayout>
@ -349,7 +349,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="通行碼"
android:text="@string/passcode"
android:textColor="@color/black"
android:textSize="25sp" />
</LinearLayout>
@ -377,7 +377,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="來店禮"
android:text="@string/gifts"
android:textColor="@color/black"
android:textSize="25sp" />
</LinearLayout>

View File

@ -23,7 +23,7 @@
android:layout_height="60dp"
android:background="@android:color/transparent"
android:gravity="center"
android:text="現金支付"
android:text="@string/cash_payment"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="bold" />
@ -47,7 +47,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="交易金額:"
android:text="@string/transaction_amount"
android:textColor="@color/black"
android:textSize="40sp"
android:textStyle="bold" />
@ -86,7 +86,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="已投入:"
android:text="@string/label_inserted"
android:textColor="@color/black"
android:textSize="40sp"
android:textStyle="bold" />
@ -124,7 +124,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="未投入:"
android:text="@string/label_not_inserted"
android:textColor="@color/black"
android:textSize="40sp"
android:textStyle="bold" />
@ -168,7 +168,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:gravity="center"
android:text="請投入硬幣或紙鈔"
android:text="@string/please_insert_cash"
android:textColor="@color/primary"
android:textSize="25sp"
android:textStyle="bold" />
@ -228,7 +228,7 @@
android:layout_weight="1"
android:backgroundTint="@color/warning"
android:text="取消並退幣(45秒)"
android:text="@string/cancel_and_refund"
android:textColor="@color/white"
android:textSize="16sp"
app:elevation="4dp"
@ -258,7 +258,7 @@
android:layout_marginStart="16dp"
android:layout_weight="1"
android:backgroundTint="@color/warning"
android:text="嘗試退鈔並重試"
android:text="@string/retry_refund"
android:textColor="@color/white"
android:textSize="16sp"
android:visibility="gone"

View File

@ -46,7 +46,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="交易金額:"
android:text="@string/transaction_amount"
android:textColor="@color/black"
android:textSize="40sp"
android:textStyle="bold" />
@ -74,7 +74,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="請將付款條碼對準掃描器"
android:text="@string/align_barcode_to_scanner"
android:textColor="@color/primary"
android:textSize="25sp"
android:textStyle="bold" />
@ -145,7 +145,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/warning"
android:text="取消支付(40秒)"
android:text="@string/cancel_payment"
android:textColor="@color/white"
android:textSize="16sp"
app:elevation="4dp"
@ -160,7 +160,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/infoContainer"
android:text="修改支付方式"
android:text="@string/change_payment_method"
android:textColor="@color/black"
android:textSize="16sp"
app:elevation="4dp"

View File

@ -46,7 +46,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="交易金額:"
android:text="@string/transaction_amount"
android:textColor="@color/black"
android:textSize="40sp"
android:textStyle="bold" />
@ -74,7 +74,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="請將付款條碼對準掃描器"
android:text="@string/align_barcode_to_scanner"
android:textColor="@color/primary"
android:textSize="25sp"
android:textStyle="bold" />
@ -143,7 +143,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/warning"
android:text="取消支付(40秒)"
android:text="@string/cancel_payment"
android:textColor="@color/white"
android:textSize="16sp"
app:elevation="4dp"
@ -158,7 +158,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/infoContainer"
android:text="修改支付方式"
android:text="@string/change_payment_method"
android:textColor="@color/black"
android:textSize="16sp"
app:elevation="4dp"

View File

@ -46,7 +46,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="交易金額:"
android:text="@string/transaction_amount"
android:textColor="@color/black"
android:textSize="40sp"
android:textStyle="bold" />
@ -74,7 +74,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="請將卡片靠近感應區"
android:text="@string/tap_card_to_reader"
android:textColor="@color/primary"
android:textSize="25sp"
android:textStyle="bold" />
@ -143,7 +143,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/warning"
android:text="取消支付(40秒)"
android:text="@string/cancel_payment"
android:textColor="@color/onWarning"
android:textSize="16sp"
app:elevation="4dp"
@ -158,7 +158,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/gray"
android:text="修改支付方式"
android:text="@string/change_payment_method"
android:textColor="@color/black"
android:textSize="16sp"
app:elevation="4dp"

View File

@ -46,7 +46,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="交易金額:"
android:text="@string/transaction_amount"
android:textColor="@color/black"
android:textSize="40sp"
android:textStyle="bold" />
@ -74,7 +74,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="請將付款條碼對準掃描器"
android:text="@string/align_barcode_to_scanner"
android:textColor="@color/primary"
android:textSize="25sp"
android:textStyle="bold" />
@ -143,7 +143,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/warning"
android:text="取消支付(40秒)"
android:text="@string/cancel_payment"
android:textColor="@color/white"
android:textSize="16sp"
app:elevation="4dp"
@ -158,7 +158,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/infoContainer"
android:text="修改支付方式"
android:text="@string/change_payment_method"
android:textColor="@color/black"
android:textSize="16sp"
app:elevation="4dp"

View File

@ -46,7 +46,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="交易金額:"
android:text="@string/transaction_amount"
android:textColor="@color/black"
android:textSize="40sp"
android:textStyle="bold" />
@ -74,7 +74,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="請將卡片靠近感應區"
android:text="@string/tap_card_to_reader"
android:textColor="@color/primary"
android:textSize="25sp"
android:textStyle="bold" />
@ -143,7 +143,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/warning"
android:text="取消支付(40秒)"
android:text="@string/cancel_payment"
android:textColor="@color/white"
android:textSize="16sp"
app:elevation="4dp"
@ -158,7 +158,7 @@
android:layout_weight="1"
android:paddingHorizontal="0dp"
android:backgroundTint="@color/infoContainer"
android:text="修改支付方式"
android:text="@string/change_payment_method"
android:textColor="@color/black"
android:textSize="16sp"
app:elevation="4dp"

View File

@ -42,7 +42,7 @@
android:layout_height="match_parent"
android:layout_weight="1.10"
android:gravity="center|end"
android:text="交易金額:"
android:text="@string/transaction_amount"
android:textColor="@color/error"
android:textSize="30sp"
android:textStyle="bold" />
@ -74,7 +74,7 @@
android:layout_marginStart="5dp"
android:layout_weight="0.90"
android:gravity="center"
android:text="QRCode產生中請等待片刻"
android:text="@string/generating_qrcode"
android:textColor="@color/error"
android:textSize="30sp"
android:textStyle="bold" />
@ -158,7 +158,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/white"
android:text="更換支付"
android:text="@string/replacement_payment"
android:textSize="20sp"
android:textColor="@color/black" />
</LinearLayout>

View File

@ -106,7 +106,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="公司統編"
android:text="@string/company_tax_id"
android:textColor="@color/primary"
android:textSize="14sp"
android:textStyle="bold" />
@ -142,7 +142,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="電話號碼"
android:text="@string/phone_number"
android:textColor="@color/gray"
android:textSize="14sp" />
</LinearLayout>
@ -162,7 +162,7 @@
android:layout_marginStart="5dp"
android:layout_weight="1"
android:gravity="center"
android:text="輸入公司統編"
android:text="@string/enter_company_tax_id"
android:textColor="?attr/colorPrimary"
android:textSize="30sp"
android:textStyle="bold" />
@ -182,7 +182,7 @@
android:layout_width="320dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="公司統編"
android:hint="@string/company_tax_id"
app:boxBackgroundMode="outline"
app:boxCornerRadiusBottomEnd="8dp"
app:boxCornerRadiusBottomStart="8dp"
@ -391,7 +391,7 @@
android:layout_width="320dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="電話號碼"
android:hint="@string/phone_number"
app:boxBackgroundMode="outline"
app:boxCornerRadiusBottomEnd="8dp"
app:boxCornerRadiusBottomStart="8dp"
@ -600,7 +600,7 @@
android:layout_weight="1"
android:layout_marginEnd="16dp"
android:backgroundTint="@color/warning"
android:text="返回支付方式"
android:text="@string/back_to_payment"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />
@ -612,7 +612,7 @@
android:layout_height="64dp"
android:layout_weight="1"
android:backgroundTint="?attr/colorPrimary"
android:text="下一步"
android:text="@string/next_step"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />

View File

@ -149,14 +149,14 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:src="@drawable/qrcode_msg_dailemei"
android:src="@drawable/qrcode_msg"
android:scaleType="fitXY" />
<TextView
android:id="@+id/tvCountdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="15s後關閉"
android:text=""
android:textColor="#FFFFFF"
android:textSize="30sp"
android:visibility="gone"

View File

@ -90,7 +90,7 @@
android:layout_marginStart="5dp"
android:layout_weight="1"
android:gravity="center"
android:text="請選擇捐贈機構"
android:text="@string/select_charity"
android:textColor="?attr/colorPrimary"
android:textSize="30sp"
android:textStyle="bold" />
@ -147,7 +147,7 @@
android:layout_weight="1"
android:layout_marginEnd="16dp"
android:backgroundTint="@color/warning"
android:text="返回支付方式"
android:text="@string/back_to_payment"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />
@ -159,7 +159,7 @@
android:layout_height="64dp"
android:layout_weight="1"
android:backgroundTint="?attr/colorPrimary"
android:text="確定"
android:text="@string/sure"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />

View File

@ -83,12 +83,12 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/btnModeKeypad"
style="@style/Widget.App.InputModeButton"
android:text="鍵盤輸入" />
android:text="@string/keyboard_input" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnModeScan"
style="@style/Widget.App.InputModeButton"
android:text="掃描條碼" />
android:text="@string/scan_barcode" />
</LinearLayout>
<LinearLayout

View File

@ -152,7 +152,7 @@
android:layout_height="64dp"
android:layout_weight="1"
android:backgroundTint="@color/warning"
android:text="返回支付方式"
android:text="@string/back_to_payment"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />
@ -166,7 +166,7 @@
android:layout_marginStart="16dp"
android:backgroundTint="?attr/colorPrimary"
android:enabled="false"
android:text="確認"
android:text="@string/confirm"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />

View File

@ -25,7 +25,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="請選擇發票開立方式"
android:text="@string/select_invoice_type"
android:textColor="@color/primary"
android:textSize="36sp"
android:textStyle="bold"
@ -70,7 +70,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="輸入載具"
android:text="@string/input_vehicle"
android:textColor="@color/black"
android:textSize="26sp"
android:textStyle="bold" />
@ -102,7 +102,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="捐贈發票"
android:text="@string/donation_invoice_next"
android:textColor="@color/black"
android:textSize="26sp"
android:textStyle="bold" />
@ -133,7 +133,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="公司統編"
android:text="@string/company_tax_id"
android:textColor="@color/black"
android:textSize="26sp"
android:textStyle="bold" />
@ -160,7 +160,7 @@
android:layout_width="match_parent"
android:layout_height="64dp"
android:backgroundTint="@color/warning"
android:text="返回40秒"
android:text="@string/return_to"
android:textColor="@color/white"
android:textSize="20sp"
app:cornerRadius="8dp" />

View File

@ -40,7 +40,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="無需付款"
android:text="@string/no_payment_needed"
android:textColor="@color/primary"
android:textSize="40sp"
android:textStyle="bold" />
@ -49,7 +49,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="請將員工卡靠近讀卡機"
android:text="@string/please_tap_member_card"
android:textColor="@color/primary"
android:textSize="25sp"
android:textStyle="bold" />
@ -60,7 +60,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="交易狀態:等待感應"
android:text="@string/waiting_for_tap"
android:textColor="@color/black"
android:textSize="15sp" />
@ -78,7 +78,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="卡號資訊:"
android:text="@string/card_number_info"
android:textSize="20sp"
android:textColor="@color/black" />
@ -89,7 +89,7 @@
android:layout_marginTop="10dp"
android:background="@drawable/textview_border"
android:gravity="center"
android:hint="請感應卡片"
android:hint="@string/tap_card_hint"
android:inputType="text"
android:textSize="30sp"
android:textColor="@color/primary"

View File

@ -27,7 +27,7 @@
android:layout_height="60dp"
android:background="@android:color/transparent"
android:gravity="center"
android:text="請確認您的商品是否正確"
android:text="@string/confirm_product_correct"
android:textColor="@color/black"
android:textSize="28sp"
android:textStyle="bold" />
@ -73,7 +73,7 @@
android:layout_weight="1"
android:layout_marginStart="16dp"
android:backgroundTint="@color/primary"
android:text="確認"
android:text="@string/confirm"
android:textColor="@color/onPrimary"
android:textSize="20sp"
app:cornerRadius="8dp" />

View File

@ -31,7 +31,7 @@
android:layout_width="match_parent"
android:layout_height="65dp"
android:gravity="center"
android:text="請輸入取貨碼"
android:text="@string/enter_pickup_code"
android:textColor="@color/black"
android:textSize="36sp"
android:textStyle="bold" />
@ -50,7 +50,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="請輸入取貨碼 或 掃描QRCode"
android:text="@string/enter_pickup_code_or_scan"
android:textColor="@color/primary"
android:textSize="24sp"
android:textStyle="bold" />
@ -83,12 +83,12 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/btnModeKeypad"
style="@style/Widget.App.InputModeButton"
android:text="鍵盤輸入" />
android:text="@string/keyboard_input" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnModeScan"
style="@style/Widget.App.InputModeButton"
android:text="掃描條碼" />
android:text="@string/scan_barcode" />
</LinearLayout>
<LinearLayout
@ -103,7 +103,7 @@
android:layout_width="320dp"
android:layout_height="60dp"
android:textSize="28sp"
android:hint="請輸入取貨碼"
android:hint="@string/enter_pickup_code"
android:background="@drawable/bg_input_outline"
android:paddingStart="16dp"
android:paddingEnd="16dp"
@ -269,7 +269,7 @@
android:insetLeft="0dp"
android:insetRight="0dp"
android:padding="0dp"
android:text="取消"
android:text="@string/cancel"
android:textSize="26sp"
app:cornerRadius="12dp"
android:textColor="@color/white" />
@ -289,7 +289,7 @@
android:insetLeft="0dp"
android:insetRight="0dp"
android:padding="0dp"
android:text="確認"
android:text="@string/confirm"
android:textSize="26sp"
app:cornerRadius="12dp"
android:textColor="@color/white" />

View File

@ -55,7 +55,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="關閉"
android:text="@string/close"
android:textColor="@color/onWarning"
android:textSize="@dimen/text_size_title_small"
android:textStyle="bold" />
@ -161,7 +161,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="商品描述"
android:text="@string/product_description"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_title_small"
android:textStyle="bold" />
@ -202,7 +202,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="數量"
android:text="@string/amount"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_title_small" />
@ -258,7 +258,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="金額小計"
android:text="@string/subtotal"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_title_small" />
@ -300,7 +300,7 @@
android:layout_height="wrap_content"
android:gravity="center"
android:padding="4dp"
android:text="加入購物車"
android:text="@string/add_to_shopping_cart"
android:textColor="@color/primary"
android:textSize="@dimen/text_size_label_large"
android:textStyle="bold" />
@ -324,7 +324,7 @@
android:layout_height="wrap_content"
android:gravity="center"
android:padding="4dp"
android:text="直接購買"
android:text="@string/buy_now"
android:textColor="@color/onPrimary"
android:textSize="@dimen/text_size_label_large"
android:textStyle="bold" />

View File

@ -141,7 +141,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="付款金額"
android:text="@string/payment_amount"
android:textColor="@color/black"
android:textSize="26sp"
android:textStyle="bold" />

View File

@ -36,7 +36,7 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:text="商品"
android:text="@string/product_label"
style="@style/TextAppearance.App.QuickCart.Medium"
android:textSize="24sp"
android:textStyle="normal" />
@ -72,7 +72,7 @@
android:id="@+id/textStock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="庫存00件"
android:text="@string/stock_label"
android:layout_marginStart="16dp"
style="@style/TextAppearance.App.QuickCart.Medium"
android:textColor="@color/gray"

View File

@ -59,7 +59,7 @@
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:background="@drawable/bg_rounded_primary"
android:text="貨道01"
android:text="@string/slot_label"
android:textColor="@color/onPrimary"
android:textSize="@dimen/text_size_label"
android:textStyle="bold" />
@ -75,7 +75,7 @@
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:background="@drawable/bg_rounded_light_gray"
android:text="庫存0"
android:text="@string/stock_label"
android:textColor="@color/black"
android:textSize="@dimen/text_size_label"
android:textStyle="bold" />
@ -151,7 +151,7 @@
android:id="@+id/textMask"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="售完"
android:text="@string/sold_out"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"

View File

@ -204,6 +204,9 @@
<!-- ui/recycler/RecyclerDialogDispatchListAdpter.java -->
<string name="shipped">Shipped</string>
<string name="shipping">Shipping</string>
<string name="shipped_quantity_format">Shipped (%1$d / %2$d)</string>
<string name="shipping_count_format">Shipping (%1$d)</string>
<string name="shipping_all">Shipping (All)</string>
<string name="waiting">Waiting</string>
<!-- ui/recycler/RecyclerProductListAdpter.java -->
<!-- ui/recycler/RecyclerProductSearchAdpter.java -->
@ -271,6 +274,7 @@
<string name="unified_number">Unified number</string>
<string name="phone_number">Phone number</string>
<string name="donation_invoice">Donation Invoice</string>
<string name="donation_invoice_next">Donation Invoice / Next</string>
<string name="input_complete">Input Complete</string>
<!-- dialog_dispatch.xml -->
<string name="shipping_goods">Shipping goods</string>
@ -391,6 +395,18 @@
<string name="product_delivery_issues_contact_the_counter_window">For product delivery issues, please contact the counter window!</string>
<string name="abnormal_reminder">Abnormal reminder</string>
<string name="there_is_something_at_the_pickup_port">There is something at the pickup port, please remove it first</string>
<string name="pickup_door_not_closed">The pickup port door is not properly closed. Please make sure it is shut.</string>
<string name="pickup_elevator_not_home">The lift has not returned to its home position. Please contact staff.</string>
<string name="pickup_status_confirming">Confirming pickup port status. Please wait 1-2 seconds and tap again.</string>
<string name="pickup_checking">Checking pickup port...</string>
<string name="enter_pickup_code_or_scan">Enter pickup code or scan QR code</string>
<string name="tap_card_hint">Please tap your card</string>
<string name="cancel_seconds_format">Cancel %1$ds</string>
<string name="verifying_card">Verifying card...</string>
<string name="product_expired_not_for_sale">This product has expired and is not available for sale.</string>
<string name="unsupported_discount_type">Unsupported discount type</string>
<string name="confirm_product_correct">Please confirm your product is correct</string>
<string name="dispatch_quantity_format">Quantity: %1$d</string>
<string name="i_knew">knew</string>
<string name="input_vehicle">Input Vehicle</string>
<string name="sync_product">Reload Products</string>
@ -435,4 +451,70 @@
<string name="please_go_to_cabinet_to_pick_up_goods">Please collect your item from cabinet No. 99</string>
<string name="confirm_goods_taken_hardcoded">Confirm item taken (15s)</string>
<string name="system_inspection_in_progress">Machine inspection in progress, please wait</string>
<!-- ===== i18n backfill: sale / product detail / cart / payment / invoice (2026-07) ===== -->
<string name="slot_label">Slot</string>
<string name="stock_label">Stock</string>
<string name="close">Close</string>
<string name="product_description">Description</string>
<string name="subtotal">Subtotal</string>
<string name="add_to_shopping_cart">Add to Cart</string>
<string name="buy_now">Buy Now</string>
<string name="payment_amount">Amount</string>
<string name="product_label">Product</string>
<string name="stock_pieces_format">Stock: %1$02d</string>
<string name="select_payment_method">Select Payment Method</string>
<string name="credit_card">Credit Card</string>
<string name="e_ticket">Stored-Value Card</string>
<string name="phone_nfc">Phone NFC</string>
<string name="qr_pay_esun">QR Pay - E.SUN</string>
<string name="qr_pay_tappay">QR Pay - TapPay</string>
<string name="line_pay">Line Pay</string>
<string name="select_invoice_type">Select Invoice Type</string>
<string name="company_tax_id">Company Tax ID</string>
<string name="select_charity">Select a Charity</string>
<string name="back_to_payment">Back to Payment</string>
<string name="back_to_invoice">Back to Invoice</string>
<string name="enter_company_tax_id">Enter Company Tax ID</string>
<string name="next_step">Next</string>
<string name="confirm">Confirm</string>
<string name="tax_id_min_8">Tax ID must be at least 8 digits</string>
<string name="phone_min_10">Phone number must be at least 10 digits</string>
<string name="enter_phone_number">Enter Phone Number</string>
<string name="pay_item_count">Quantity: %d item(s)</string>
<string name="pay_payable_amount">Amount Due</string>
<string name="pay_discounted_format">Saved NT$%d</string>
<!-- ===== i18n backfill batch2 ===== -->
<string name="change_payment_method">Change Payment Method</string>
<string name="cancel_payment">Cancel Payment</string>
<string name="align_barcode_to_scanner">Align the payment barcode with the scanner</string>
<string name="tap_card_to_reader">Tap your card on the reader</string>
<string name="generating_qrcode">Generating QR code, please wait</string>
<string name="card_reader_cannot_open">Cannot start card reader</string>
<string name="reading_transaction">Reading transaction. Keep your card/phone steady,\ndo not move quickly; it will return automatically when done.</string>
<string name="dispensing_product">Dispensing product</string>
<string name="close_after_seconds">Close in (%1$ds)</string>
<string name="keyboard_input">Keyboard</string>
<string name="scan_barcode">Scan Barcode</string>
<string name="verifying">Verifying…</string>
<string name="pickup_code_min_8">Pickup code must be at least 8 digits</string>
<string name="passcode_min_8">Passcode must be at least 8 digits</string>
<string name="gift_code_min_8">Gift code must be 8 digits</string>
<string name="enter_pickup_code">Enter pickup code</string>
<string name="pickup_code_not_enabled">Pickup code is not enabled</string>
<string name="passcode_not_enabled">Passcode is not enabled</string>
<string name="gift_not_enabled">Gift function is not enabled</string>
<string name="no_payment_needed">No payment required</string>
<string name="card_number_info">Card No.:</string>
<string name="product_sold_out_reselect">Product sold out, please choose again</string>
<string name="waiting_for_tap">Status: waiting for tap</string>
<string name="label_inserted">Inserted</string>
<string name="label_not_inserted">Remaining</string>
<string name="please_insert_cash">Please insert coins or bills</string>
<string name="cancel_and_refund">Cancel &amp; Refund</string>
<string name="retry_refund">Try refund again</string>
<string name="hint_title">Notice!</string>
<string name="device_initializing">Initializing device, please wait...</string>
<string name="go_to_cabinet_format">Please collect your item at cabinet No. %1$s</string>
<string name="confirm_taken_format">Confirm item taken (%1$ds)</string>
</resources>

View File

@ -204,6 +204,9 @@
<!-- ui/recycler/RecyclerDialogDispatchListAdpter.java -->
<string name="shipped">発送済み</string>
<string name="shipping">配送</string>
<string name="shipped_quantity_format">出荷済み(%1$d / %2$d</string>
<string name="shipping_count_format">出荷中(%1$d点</string>
<string name="shipping_all">出荷中(All)</string>
<string name="waiting">待っている</string>
<!-- ui/recycler/RecyclerProductListAdpter.java -->
<!-- ui/recycler/RecyclerProductSearchAdpter.java -->
@ -271,6 +274,7 @@
<string name="unified_number">統一番号</string>
<string name="phone_number">電話番号</string>
<string name="donation_invoice">寄付金請求書</string>
<string name="donation_invoice_next">寄付金請求書/次へ</string>
<string name="input_complete">入力完了</string>
<!-- dialog_dispatch.xml -->
<string name="shipping_goods">商品の発送</string>
@ -391,6 +395,18 @@
<string name="product_delivery_issues_contact_the_counter_window">商品配送に関するトラブルはカウンター窓口までお問い合わせください!</string>
<string name="abnormal_reminder">異常なリマインダー</string>
<string name="there_is_something_at_the_pickup_port">受取口に物があります、先に取り出してください</string>
<string name="pickup_door_not_closed">受取口の扉が正しく閉まっていません。扉が閉まっているか確認してください。</string>
<string name="pickup_elevator_not_home">リフトが元の位置に戻っていません。係員にお問い合わせください。</string>
<string name="pickup_status_confirming">受取口の状態を確認中です。1〜2秒待ってからもう一度タップしてください。</string>
<string name="pickup_checking">受取口を確認中......</string>
<string name="enter_pickup_code_or_scan">受取コードを入力、またはQRコードをスキャン</string>
<string name="tap_card_hint">カードをかざしてください</string>
<string name="cancel_seconds_format">キャンセル %1$d秒</string>
<string name="verifying_card">カードを確認中...</string>
<string name="product_expired_not_for_sale">商品は有効期限切れのため、販売しておりません。</string>
<string name="unsupported_discount_type">サポートされていない割引タイプ</string>
<string name="confirm_product_correct">商品が正しいかご確認ください</string>
<string name="dispatch_quantity_format">出荷数量:%1$d点</string>
<string name="i_knew">知っていた</string>
<string name="input_vehicle">スマホバーコード への入力</string>
<string name="sync_product">商品データを同期</string>
@ -434,4 +450,70 @@
<string name="please_go_to_cabinet_to_pick_up_goods">99番の扉から商品をお受け取りください</string>
<string name="confirm_goods_taken_hardcoded">受け取り確認15秒</string>
<string name="system_inspection_in_progress">機器点検中です。しばらくお待ちください</string>
<!-- ===== i18n backfill: sale / product detail / cart / payment / invoice (2026-07) ===== -->
<string name="slot_label">レーン</string>
<string name="stock_label">在庫</string>
<string name="close">閉じる</string>
<string name="product_description">商品説明</string>
<string name="subtotal">小計</string>
<string name="add_to_shopping_cart">カートに追加</string>
<string name="buy_now">今すぐ購入</string>
<string name="payment_amount">お支払金額</string>
<string name="product_label">商品</string>
<string name="stock_pieces_format">在庫%1$02d個</string>
<string name="select_payment_method">お支払方法を選択</string>
<string name="credit_card">クレジットカード</string>
<string name="e_ticket">電子マネー</string>
<string name="phone_nfc">スマホNFC</string>
<string name="qr_pay_esun">QR決済-玉山</string>
<string name="qr_pay_tappay">QR決済-TapPay</string>
<string name="line_pay">Line Pay</string>
<string name="select_invoice_type">請求書の発行方法を選択</string>
<string name="company_tax_id">法人統一番号</string>
<string name="select_charity">寄付先を選択してください</string>
<string name="back_to_payment">お支払方法に戻る</string>
<string name="back_to_invoice">請求書選択に戻る</string>
<string name="enter_company_tax_id">法人統一番号を入力</string>
<string name="next_step">次へ</string>
<string name="confirm">確認</string>
<string name="tax_id_min_8">統一番号は8桁以上必要です</string>
<string name="phone_min_10">電話番号は10桁以上必要です</string>
<string name="enter_phone_number">電話番号を入力</string>
<string name="pay_item_count">商品数量:計 %d 点</string>
<string name="pay_payable_amount">お支払金額</string>
<string name="pay_discounted_format">NT$%d 割引</string>
<!-- ===== i18n backfill batch2 ===== -->
<string name="change_payment_method">お支払方法を変更</string>
<string name="cancel_payment">支払いをキャンセル</string>
<string name="align_barcode_to_scanner">決済バーコードをスキャナーに合わせてください</string>
<string name="tap_card_to_reader">カードをリーダーにかざしてください</string>
<string name="generating_qrcode">QRコードを生成中です。お待ちください</string>
<string name="card_reader_cannot_open">カードリーダーを起動できません</string>
<string name="reading_transaction">取引を読み取り中です。カード/スマホを動かさず、\n完了後は自動的に戻ります。</string>
<string name="dispensing_product">商品を排出中</string>
<string name="close_after_seconds">%1$d秒後に閉じる</string>
<string name="keyboard_input">キーボード入力</string>
<string name="scan_barcode">バーコードをスキャン</string>
<string name="verifying">確認中…</string>
<string name="pickup_code_min_8">受取コードは8桁以上必要です</string>
<string name="passcode_min_8">通行コードは8桁以上必要です</string>
<string name="gift_code_min_8">来店特典コードは8桁必要です</string>
<string name="enter_pickup_code">受取コードを入力</string>
<string name="pickup_code_not_enabled">受取コード機能は未開放です</string>
<string name="passcode_not_enabled">通行コード機能は未開放です</string>
<string name="gift_not_enabled">来店特典機能は未開放です</string>
<string name="no_payment_needed">お支払い不要</string>
<string name="card_number_info">カード番号:</string>
<string name="product_sold_out_reselect">商品は完売しました。もう一度お選びください</string>
<string name="waiting_for_tap">状態:タッチ待ち</string>
<string name="label_inserted">投入済み</string>
<string name="label_not_inserted">未投入</string>
<string name="please_insert_cash">硬貨または紙幣を投入してください</string>
<string name="cancel_and_refund">キャンセルして返金</string>
<string name="retry_refund">返金を再試行</string>
<string name="hint_title">お知らせ!</string>
<string name="device_initializing">機器を初期化中です。お待ちください...</string>
<string name="go_to_cabinet_format">%1$s番の扉から商品をお取りください</string>
<string name="confirm_taken_format">受取確認(%1$d秒</string>
</resources>

View File

@ -126,6 +126,9 @@
<!-- ui/recycler/RecyclerDialogDispatchListAdpter.java -->
<string name="shipped">已出貨</string>
<string name="shipping">出貨中</string>
<string name="shipped_quantity_format">已出貨數量(%1$d / %2$d</string>
<string name="shipping_count_format">出貨中(%1$d件</string>
<string name="shipping_all">出貨中(All)</string>
<string name="waiting">等待中</string>
<!-- ui/recycler/RecyclerProductSearchAdpter.java -->
<string name="click_on_product">點選商品</string>
@ -182,6 +185,7 @@
<string name="unified_number">統一編號</string>
<string name="phone_number">電話號碼</string>
<string name="donation_invoice">捐贈發票</string>
<string name="donation_invoice_next">捐贈發票/下一步</string>
<string name="input_complete">輸入完成</string>
<!-- dialog_dispatch.xml -->
<string name="shipping_goods">出貨商品</string>
@ -302,6 +306,18 @@
<string name="product_delivery_issues_contact_the_counter_window">商品出貨問題,請洽櫃台窗口!</string>
<string name="abnormal_reminder">異常提醒</string>
<string name="there_is_something_at_the_pickup_port">取貨口有東西,請先取出</string>
<string name="pickup_door_not_closed">取貨口門未正常關閉,請先確認門已關好。</string>
<string name="pickup_elevator_not_home">升降梯未回到原點,請洽服務人員處理。</string>
<string name="pickup_status_confirming">正在確認取貨口狀態,請稍候 1~2 秒再點選一次。</string>
<string name="pickup_checking">取貨口檢查中......</string>
<string name="enter_pickup_code_or_scan">請輸入取貨碼 或 掃描QRCode</string>
<string name="tap_card_hint">請感應卡片</string>
<string name="cancel_seconds_format">取消 %1$d秒</string>
<string name="verifying_card">正在驗證卡片...</string>
<string name="product_expired_not_for_sale">商品已過效期,暫不販售</string>
<string name="unsupported_discount_type">不支援的折扣類型</string>
<string name="confirm_product_correct">請確認您的商品是否正確</string>
<string name="dispatch_quantity_format">出貨數量:%1$d件</string>
<string name="i_knew">知道了</string>
<string name="input_vehicle">輸入載具</string>
<string name="member_card_pickup_title">員工卡支付</string>
@ -348,4 +364,67 @@
<string name="please_go_to_cabinet_to_pick_up_goods">請至99號櫃門拿取您的商品</string>
<string name="confirm_goods_taken_hardcoded">確認已取貨15秒</string>
<string name="system_inspection_in_progress">機器巡檢中請稍後</string>
<!-- ===== i18n 補齊:銷售/商品詳情/購物車/支付/發票流程 硬編字改資源 (2026-07) ===== -->
<string name="slot_label">貨道</string>
<string name="stock_label">庫存</string>
<string name="close">關閉</string>
<string name="product_description">商品描述</string>
<string name="subtotal">金額小計</string>
<string name="add_to_shopping_cart">加入購物車</string>
<string name="buy_now">直接購買</string>
<string name="payment_amount">付款金額</string>
<string name="product_label">商品</string>
<string name="stock_pieces_format">庫存%1$02d件</string>
<string name="select_payment_method">請選擇支付方式</string>
<string name="credit_card">信用卡</string>
<string name="e_ticket">電子票證</string>
<string name="phone_nfc">手機NFC</string>
<string name="qr_pay_esun">掃碼支付-玉山</string>
<string name="qr_pay_tappay">掃碼支付-TapPay</string>
<string name="line_pay">Line Pay 支付</string>
<string name="select_invoice_type">請選擇發票開立方式</string>
<string name="company_tax_id">公司統編</string>
<string name="select_charity">請選擇捐贈機構</string>
<string name="back_to_payment">返回支付方式</string>
<string name="back_to_invoice">返回發票選擇</string>
<string name="enter_company_tax_id">輸入公司統編</string>
<string name="next_step">下一步</string>
<string name="confirm">確認</string>
<string name="tax_id_min_8">統一編號長度不能少於 8 碼</string>
<string name="phone_min_10">電話號碼長度不能少於 10 碼</string>
<string name="enter_phone_number">輸入電話號碼</string>
<!-- ===== i18n 補齊 batch2支付對話框/出貨/碼輸入/員工卡/取貨成功 (2026-07) ===== -->
<string name="change_payment_method">修改支付方式</string>
<string name="cancel_payment">取消支付</string>
<string name="align_barcode_to_scanner">請將付款條碼對準掃描器</string>
<string name="tap_card_to_reader">請將卡片靠近感應區</string>
<string name="generating_qrcode">QRCode產生中請等待片刻</string>
<string name="card_reader_cannot_open">刷卡機無法開啟</string>
<string name="reading_transaction">交易讀取中,請將卡片/手機停留片刻,\n勿快速移動系統完成後會自動返回。</string>
<string name="dispensing_product">商品出貨中</string>
<string name="close_after_seconds">%1$ds後關閉</string>
<string name="keyboard_input">鍵盤輸入</string>
<string name="scan_barcode">掃描條碼</string>
<string name="verifying">驗證中…</string>
<string name="pickup_code_min_8">取貨碼長度不能少於 8 碼</string>
<string name="passcode_min_8">通行碼長度不能少於 8 碼</string>
<string name="gift_code_min_8">來店禮代碼需 8 碼</string>
<string name="enter_pickup_code">請輸入取貨碼</string>
<string name="pickup_code_not_enabled">取貨碼功能未開放</string>
<string name="passcode_not_enabled">通行碼功能未開放</string>
<string name="gift_not_enabled">來店禮功能未開放</string>
<string name="no_payment_needed">無需付款</string>
<string name="card_number_info">卡號資訊:</string>
<string name="product_sold_out_reselect">商品已售罄,請重新選擇</string>
<string name="waiting_for_tap">交易狀態:等待感應</string>
<string name="label_inserted">已投入</string>
<string name="label_not_inserted">未投入</string>
<string name="please_insert_cash">請投入硬幣或紙鈔</string>
<string name="cancel_and_refund">取消並退幣</string>
<string name="retry_refund">嘗試退鈔並重試</string>
<string name="hint_title">提示!</string>
<string name="device_initializing">設備初始化中,請稍候...</string>
<string name="go_to_cabinet_format">請至%1$s號櫃門拿取您的商品</string>
<string name="confirm_taken_format">確認已取貨(%1$d秒</string>
</resources>