Merge remote-tracking branch 'origin/feat/general-cart-and-payment' into ui-design

This commit is contained in:
TaiwanStar Developer 2026-06-16 13:52:36 +08:00
commit dd87f1ca6e
6 changed files with 149 additions and 26 deletions

View File

@ -10,7 +10,7 @@ plugins {
//
// =========================================================================
def verMinor = 1
def verPatch = 93
def verPatch = 96

View File

@ -1130,7 +1130,12 @@ public class StarCloudAPI {
Log.i(TAG, "persistCurrentSettings() - Saving to DB: machineID=" + MyApp.getInstance().getMachine().getMachineID()
+ ", token=" + MyApp.getInstance().getMachine().getApiKey());
SettingsDao settingsDao = new SettingsDao(handlerMain.getContext());
settingsDao.deleteAll();
// 只刪除本方法接下來要重寫的這 4 個設定 key避免 deleteAll() 連坐刪掉廣告(VideoEoxPlayer)等其他持久資料
// 否則遠端更新設定重啟會因廣告列被清空且啟動不會主動抓 B005導致廣告消失
settingsDao.deleteWhere(MyApp.getInstance().getMachine().getClass().getSimpleName());
settingsDao.deleteWhere(MyApp.getInstance().getEsunpay().getClass().getSimpleName());
settingsDao.deleteWhere(MyApp.getInstance().getGreenInvoice().getClass().getSimpleName());
settingsDao.deleteWhere(MyApp.getInstance().getGreenpaySetting().getClass().getSimpleName());
settingsDao.insertOne(new SettingStructure(MyApp.getInstance().getMachine().getClass().getSimpleName(),
new String[] {

View File

@ -47,6 +47,14 @@ public class TransactionFinalizeBuilder {
/** 累積的出貨結果清單 */
private final List<TransactionFinalizePayload.DispenseRecord> dispenseRecords = new ArrayList<>();
/**
* 商品明細快照buyList 仍完整時擷取例如進入出貨流程當下
* 之後 build() 會優先採用此快照不再即時讀 live buyList
* 用以修正掃碼支付(玉山)等較慢流程結案前 buyList 可能被取消/倒數路徑清空
* 導致 order.items 變成空陣列出現無商品紀錄但出貨其實成功
*/
private List<TransactionFinalizePayload.ItemRecord> itemsSnapshot = null;
private String codeId = "0";
/** 交易生命週期狀態pending/completed/failed/abandoned預設 completed 維持成功路徑原行為。 */
@ -151,6 +159,42 @@ public class TransactionFinalizeBuilder {
}
/**
* 擷取目前 buyList 的商品明細快照
* 應在buyList 仍完整時呼叫例如 DispatchDialog 進入出貨流程當下
* 之後 {@link #build()} 會優先採用此快照不再即時讀 live buyList
* 避免結案前 buyList 被取消/倒數等其他執行緒清空導致 order.items 變成空陣列
* buyList 為空時不覆寫既有快照避免把有效快照洗成空
*/
public void captureItems() {
List<BuyStructure> buyList = MyApp.getInstance().getBuyList();
if (buyList == null || buyList.isEmpty()) {
return;
}
this.itemsSnapshot = buildItemRecords(buyList);
Log.i(TAG, "captureItems: snapshot " + this.itemsSnapshot.size()
+ " item(s), flow_id=" + flowId);
}
/** 由 buyList 組出 items 明細captureItems 與 build() fallback 共用)。 */
private List<TransactionFinalizePayload.ItemRecord> buildItemRecords(List<BuyStructure> buyList) {
List<TransactionFinalizePayload.ItemRecord> items = new ArrayList<>();
if (buyList != null) {
for (BuyStructure buy : buyList) {
int productId = 0;
try {
productId = Integer.parseInt(buy.getProduct().getProductID());
} catch (NumberFormatException e) {
Log.w(TAG, "Cannot parse productID to int: " + buy.getProduct().getProductID());
}
int price = buy.getProduct().getRealPrice();
int quantity = buy.getCount() > 0 ? buy.getCount() : 1;
items.add(new TransactionFinalizePayload.ItemRecord(productId, price, quantity));
}
}
return items;
}
/**
* 加入一筆出貨結果記錄每個貨道出貨後呼叫一次
*
@ -309,20 +353,13 @@ public class TransactionFinalizeBuilder {
order.setMachine_time(orderMachineTime);
// --- 組裝購買商品清單 (items) ---
List<TransactionFinalizePayload.ItemRecord> items = new ArrayList<>();
List<BuyStructure> buyList = MyApp.getInstance().getBuyList();
if (buyList != null) {
for (BuyStructure buy : buyList) {
int productId = 0;
try {
productId = Integer.parseInt(buy.getProduct().getProductID());
} catch (NumberFormatException e) {
Log.w(TAG, "Cannot parse productID to int: " + buy.getProduct().getProductID());
}
int price = buy.getProduct().getRealPrice();
int quantity = buy.getCount() > 0 ? buy.getCount() : 1;
items.add(new TransactionFinalizePayload.ItemRecord(productId, price, quantity));
}
// 優先採用 captureItems() 在出貨流程開始時擷取的快照無快照時才即時讀 live buyList
// 快照可避免結案前 buyList 被其他執行緒清空掃碼支付等慢流程的競態造成無商品紀錄
List<TransactionFinalizePayload.ItemRecord> items;
if (itemsSnapshot != null && !itemsSnapshot.isEmpty()) {
items = new ArrayList<>(itemsSnapshot);
} else {
items = buildItemRecords(MyApp.getInstance().getBuyList());
}
order.setItems(items);

View File

@ -335,8 +335,8 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
getLogs().update("Temperature", String.valueOf(DevXinYuanController.getTemperature()), "");
// 同步更新機台資訊列
binding.textMachineInfoName.setText(MyApp.getInstance().getMachine().getMachineID());
// 待日後動態更改binding.textMachineInfoNetwork.setText(getString(R.string.network) + "" + networkDevice);
updateMachineInfoName();
updateMachineInfoNetwork();
binding.textMachineInfoTemperature.setText(getString(R.string.temp) + ":" + tempStr);
try {
@ -392,7 +392,7 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
case SET_NETWORK:
getLogs().info("網路:" + message);
binding.textNetwork.setText(message);
// 待日後動態更改binding.textMachineInfoNetwork.setText(message);
updateMachineInfoNetwork();
break;
case RESET_SLOTNO:
new ResetSlotNo(this).start();
@ -642,6 +642,80 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
return -1; // 無法獲取訊號強度
}
/** 取得 App 版本名稱versionName取不到時回 "0"。 */
private String getAppVersionName() {
try {
return getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
return "0";
}
}
/** 機台資訊列左側:顯示「序號 + 版本號」。 */
private void updateMachineInfoName() {
if (binding == null) {
return;
}
binding.textMachineInfoName.setText(
MyApp.getInstance().getMachine().getMachineID() + " Ver " + getAppVersionName());
}
/**
* 依系統/下位機回報的網路狀態動態更新機台資訊列的網路訊號 icon 與文字
* WiFi RSSI(dBm) 1~3 格與滿格行動網路(SIM) dBm 0~4 無網路顯示 0
*/
private void updateMachineInfoNetwork() {
if (binding == null) {
return;
}
int iconRes = R.drawable.signal_cellular_0_bar_24; // 預設無網路
String label = getString(R.string.no_internet);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = connectivityManager.getActiveNetwork();
NetworkCapabilities capabilities = (network != null)
? connectivityManager.getNetworkCapabilities(network) : null;
if (capabilities != null
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
int rssi = 0; // 0 代表系統未提供強度
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
rssi = capabilities.getSignalStrength();
}
iconRes = wifiIconByRssi(rssi);
label = getString(R.string.wireless_internet);
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
iconRes = cellularIconByDbm(getMobileSignalStrength());
label = getString(R.string.mobile_network);
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
iconRes = R.drawable.network_wifi_24; // 有線以滿格示意連線正常
label = getString(R.string.wired_internet);
}
}
}
binding.textMachineInfoNetwork.setText(label);
binding.textMachineInfoNetwork.setCompoundDrawablesRelativeWithIntrinsicBounds(iconRes, 0, 0, 0);
}
/** WiFi RSSI(dBm) → icon數字越接近 0 越強rssi==0 視為未提供強度回滿格。 */
private int wifiIconByRssi(int rssi) {
if (rssi == 0 || rssi >= -55) return R.drawable.network_wifi_24; // 滿格
if (rssi >= -66) return R.drawable.network_wifi_3_bar_24; // 等級3
if (rssi >= -77) return R.drawable.network_wifi_2_bar_24; // 等級2
return R.drawable.network_wifi_1_bar_24; // 等級1
}
/** 行動網路 dBm → icon數字越接近 0 越強,-1 代表取不到訊號。 */
private int cellularIconByDbm(int dbm) {
if (dbm == -1) return R.drawable.signal_cellular_0_bar_24; // 無網路
if (dbm >= -85) return R.drawable.signal_cellular_4_bar_24; // 滿格
if (dbm >= -95) return R.drawable.signal_cellular_3_bar_24; // 等級3
if (dbm >= -105) return R.drawable.signal_cellular_2_bar_24; // 等級2
if (dbm >= -115) return R.drawable.signal_cellular_1_bar_24; // 等級1
return R.drawable.signal_cellular_0_bar_24; // 無網路
}
private void setBottomNavigation() {
vmcFragment = new VmcFragment(getHandlerMain());
activeFragment = vmcFragment;
@ -772,8 +846,9 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
binding.btnswitchlanTW.setOnClickListener(v -> showLanguagePicker());
// 新增機台資訊列的語言按鈕
binding.textMachineInfoLang.setOnClickListener(v -> showLanguagePicker());
// 機台資訊列一動就顯示機台 ID
binding.textMachineInfoName.setText(MyApp.getInstance().getMachine().getMachineID());
// 機台資訊列顯示序號 + 版本號
updateMachineInfoName();
updateMachineInfoNetwork();
String currentLang = LanguageHelper.getSavedLanguage(this);
binding.textMachineInfoLangText.setText(LanguageHelper.getDisplayName(currentLang));
@ -785,7 +860,8 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
finish();
};
binding.textNetwork.setOnTouchListener((v, event) -> {
// 後門已遷至機台資訊列的序號 + 版本號元素舊的 textNetwork 整條系統列已隱藏不再接收觸控
binding.textMachineInfoName.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case android.view.MotionEvent.ACTION_DOWN:
networkHandler.postDelayed(networkRunnable, 2000); // 2秒
@ -843,7 +919,8 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
binding.btnswitchlanJa.setVisibility(View.GONE);
binding.btnswitchlanTW.setOnClickListener(v -> showLanguagePicker());
binding.textMachineInfoLang.setOnClickListener(v -> showLanguagePicker());
binding.textMachineInfoName.setText(MyApp.getInstance().getMachine().getMachineID());
updateMachineInfoName();
updateMachineInfoNetwork();
String currentLang = LanguageHelper.getSavedLanguage(this);
binding.textMachineInfoLangText.setText(LanguageHelper.getDisplayName(currentLang));
}

View File

@ -258,6 +258,10 @@ public class DispatchDialog extends DialogAbstract {
httpAPI = new HttpAPI(getHandlerMain());
// 初始化 Finalize Builder在支付完成進入出貨流程時建立
finalizeBuilder = new TransactionFinalizeBuilder();
// 進入出貨流程當下 buyList 必為完整下方即用它組 dispatchList先擷取商品明細快照
// 之後結案 build() 會優先採用此快照避免掃碼支付(玉山)等慢流程在結案前 buyList
// 被取消/倒數路徑清空導致 order.items 變空無商品紀錄但出貨其實成功
finalizeBuilder.captureItems();
if (com.unibuy.smartdevice.AppEntry.isCmuh()) {
MyApp.getInstance().getReportFlowInfoData().setFlowType(42);
}

View File

@ -111,9 +111,9 @@
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="--"
android:textColor="@color/primary"
android:textSize="27sp"
android:textStyle="bold"
android:textColor="@color/gray"
android:textSize="14sp"
android:alpha="0.7"
android:singleLine="true"
android:ellipsize="end" />