[REFACTOR] 收斂 project 維度為執行期 shopping_mode + B014 雲端設定唯讀同步

1. B014 雲端設定下發 → App 解析寫入 DevSet 並持久化:
   - StarCloudAPI.saveSettings() 解析 DevSet(14 開關)/CashSet(7 面額)/
     FunctionSet(取貨碼 PickupCode、通行碼 PassCode)/ShoppingMode;
     缺漏粒度區分(整個物件缺→不動本地防誤關;物件內 key 缺→視為 false)。
   - MyApp 修 TapPay30~34 持久化 bug(saveSystemSet 補序列化、initialSystemSet
     改 optBoolean 防舊紀錄 crash),並新增 PickupCode/PassCode/ShoppingMode 持久化。
   - DevSetStructure 新增 isPickupCode/isPassCode 欄位。
   - SystemSetActivity 全部 21 個 checkbox 鎖唯讀、返回鈕只 finish() 不回寫。
   - 取貨碼浮鈕(FontendActivity)改吃 DevSet.isPickupCode()、通行碼入口(BuyDialog)
     改吃 DevSet.isPassCode(),取代舊 flavor 資源/寫死。

2. 收掉 project flavor 維度,三種購買流程改由雲端 shopping_mode 執行期切換:
   - SaleFlowHandler 改為 main 抽象基底 + create() factory,依 shopping_mode 回傳
     BasicSaleFlow(basic/原 General)、EmployeeCardSaleFlow(employee_card/原 Chengwai)、
     PickupSheetSaleFlow(pickup_sheet/原 Cmuh) 三策略。
   - 三 flavor 的 dialog/adapter/Kotlin 購物車/Cmuh 醫令類別全併入 main;重複類別去重。
   - FontendPickupCodeDialog 內部依 shopping_mode 分支(basic/employee 走 B660、
     pickup_sheet 走舊 memberVerification 並跳過取貨口檢查)。
   - fragment_vmc 合併掃描單據按鈕,VmcFragment 依 shopping_mode toggle 可見性。
   - General strings(zh/en/ja)、drawable、購物車 layout 併入 main;
     devset_defaults 併為 basic-mode bootstrap 預設。

3. 取代 BuildConfig.FLAVOR_project 編譯期分支:
   - AppEntry.isCmuh() 改判 shopping_mode == pickup_sheet。
   - DispatchDialog 6 處 Cmuh 分支改用 AppEntry.isCmuh()。
   - ComPort.getFilepath() 改認硬體維度 BuildConfig.FLAVOR(S12XYU→ttyS7/S6、
     S9XYU→ttyS1/S2),與購買流程正交;對現役機台行為不變。

4. build.gradle 移除 project 維度,flavorDimensions 只留 hardware;輸出檔名去掉
   project 段(app-S_12_XY_U-<ver>-debug.apk)。刪除 app/src/{General,Chengwai,Cmuh}/。

5. compiler-apk / release-apk(workflow + .claude/commands)改以硬體規格 S9/S12 為
   打包對象,OTA keyin 改依硬體分版並提示 shopping_mode 部署鐵律。

6. verPatch 69→70(供 OTA 遠端更新)。

雙硬體 clean 編譯回歸 S12XYU/S9XYU 全綠。實機三流程驗證待硬體。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-10 15:28:59 +08:00
parent 2c3255cb64
commit ab3b53da99
64 changed files with 519 additions and 3246 deletions

View File

@ -4,13 +4,19 @@ description: 自動編譯 Android APK、輸出至專屬目錄並安裝至實體
# 編譯與安裝 APK 工作流 (compiler-apk)
這個 Workflow 主要是協助您一鍵完成 Android 專案的編譯、匯出與安裝。當您呼叫 `/compiler-apk`AI 會自動為您執行以下步驟:
這個 Workflow 協助您一鍵完成 Android 專案的編譯、匯出與安裝。當您呼叫 `/compiler-apk`AI 會自動執行下列步驟。
> [!IMPORTANT]
> **客戶專案(購買流程)已收斂為執行期 `shopping_mode`(雲端 B014 下發),不再用 flavor 分家。**
> 打包對象只剩 **硬體規格hardware flavor**`S12XYU`10 吋)/ `S9XYU`21.5 吋 / Android 9
> 同一支 APK 在現場會依雲端 `shopping_mode` 自動切換為 basic購物車/ employee_card員工卡/ pickup_sheet取貨單
> [!TIP]
> **支援智慧硬體判斷與參數化覆寫:**
> * 預設(不帶參數):編譯 **`Chengwai` 晟崴版 (S12XYU)**。
> * **智慧判斷**:當編譯 **`中國醫 (Cmuh)`** 時,會自動識別並預設編譯 **`S9XYU` (Android 9 / 21.5 吋)**;編譯 **`General`** 與 **`Chengwai`** 時則預設編譯 **`S12XYU` (10 吋)**。
> * **參數手動覆寫**:支援手動指定硬體尺寸。例如 `/compiler-apk Cmuh S12` 可強行編譯中國醫的 10 吋版本;`/compiler-apk General S9` 可強行編譯通用版的大螢幕 21.5 吋版本。
> **參數 = 硬體規格:**
> * 預設(不帶參數):編譯 **`S12XYU`10 吋)**。
> * `/compiler-apk S9`(或 `S9XYU` / `21.5` / `大螢幕`)→ 編譯 **`S9XYU`21.5 吋 / Android 9**。
> * `/compiler-apk S12`(或 `S12XYU` / `10`)→ 編譯 **`S12XYU`10 吋)**。
> * 相容舊用語:`中國醫`→S9XYU、`晟崴`/`通用`→S12XYU僅作硬體對應流程仍由雲端決定
## 步驟零:自動檢查變更與升級版號
在開始編譯之前AI 助手會自動檢查是否有程式碼變更,以落實實體機台的 OTA 升級規範:
@ -19,7 +25,7 @@ description: 自動編譯 Android APK、輸出至專屬目錄並安裝至實體
3. 遞增完成後,向使用者呈報最新的版本號資訊,接著繼續執行步驟一。
## 步驟一:設定環境並編譯 Debug APK
使用 Java 17 與 Gradle 編譯對應 Flavor 與硬體規格的 Debug 程式碼。
使用 Java 17 與 Gradle 編譯對應硬體規格的 Debug 程式碼。
```bash
// turbo
@ -39,50 +45,22 @@ fi
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
# 取得目標 Flavor 參數
# 取得目標硬體規格(預設 S12XYU接受 S9/S12 與相容舊用語)
RAW_INPUT="$1"
TARGET_FLAVOR="Chengwai"
if [ -n "$RAW_INPUT" ]; then
LOWER_INPUT=$(echo "$RAW_INPUT" | tr '[:upper:]' '[:lower:]')
if [ "$LOWER_INPUT" = "general" ] || [ "$LOWER_INPUT" = "通用" ] || [ "$LOWER_INPUT" = "通用版" ] || [ "$LOWER_INPUT" = "標準" ] || [ "$LOWER_INPUT" = "標準版" ]; then
TARGET_FLAVOR="General"
elif [ "$LOWER_INPUT" = "chengwai" ] || [ "$LOWER_INPUT" = "晟崴" ] || [ "$LOWER_INPUT" = "晟崴版" ]; then
TARGET_FLAVOR="Chengwai"
elif [ "$LOWER_INPUT" = "cmuh" ] || [ "$LOWER_INPUT" = "中國醫" ] || [ "$LOWER_INPUT" = "中國醫版" ]; then
TARGET_FLAVOR="Cmuh"
else
TARGET_FLAVOR=$(echo "$RAW_INPUT" | awk '{print toupper(substr($0,1,1))tolower(substr($0,2))}')
fi
fi
# 根據 Flavor 動態設定智慧硬體規格預設值
TARGET_HARDWARE="S12XYU"
if [ "$TARGET_FLAVOR" = "Cmuh" ]; then
TARGET_HARDWARE="S9XYU"
if [ -n "$RAW_INPUT" ]; then
LOWER=$(echo "$RAW_INPUT" | tr '[:upper:]' '[:lower:]')
case "$LOWER" in
s9|s9xyu|21.5|大螢幕|中國醫|中國醫版|cmuh) TARGET_HARDWARE="S9XYU" ;;
s12|s12xyu|10|晟崴|晟崴版|chengwai|通用|通用版|general) TARGET_HARDWARE="S12XYU" ;;
*) echo "⚠️ 無法識別「$RAW_INPUT」沿用預設 S12XYU" ;;
esac
fi
# 取得手動指定的第二個硬體規格參數 (S9 / S12)
RAW_HW_INPUT="$2"
if [ -n "$RAW_HW_INPUT" ]; then
LOWER_HW=$(echo "$RAW_HW_INPUT" | tr '[:upper:]' '[:lower:]')
if [ "$LOWER_HW" = "s9" ] || [ "$LOWER_HW" = "s9xyu" ] || [ "$LOWER_HW" = "大螢幕" ] || [ "$LOWER_HW" = "21.5" ]; then
TARGET_HARDWARE="S9XYU"
elif [ "$LOWER_HW" = "s12" ] || [ "$LOWER_HW" = "s12xyu" ] || [ "$LOWER_HW" = "10" ]; then
TARGET_HARDWARE="S12XYU"
fi
fi
# 安全防護:檢查是否為支援的客製項目
if [ "$TARGET_FLAVOR" != "General" ] && [ "$TARGET_FLAVOR" != "Chengwai" ] && [ "$TARGET_FLAVOR" != "Cmuh" ]; then
echo "❌ 不支援的 Flavor: $TARGET_FLAVOR請使用 General, Chengwai 或 Cmuh。"
exit 1
fi
echo "🚀 開始編譯: ${TARGET_FLAVOR}${TARGET_HARDWARE}Debug"
echo "🚀 開始編譯: assemble${TARGET_HARDWARE}Debug"
# 使用 clean 確保完整重新編譯,避免舊快取遮蔽 import 缺失等錯誤
./gradlew clean :app:assemble${TARGET_FLAVOR}${TARGET_HARDWARE}Debug --no-daemon
./gradlew clean :app:assemble${TARGET_HARDWARE}Debug --no-daemon
```
## 步驟二:導出 APK 到 outputs 資料夾
@ -93,80 +71,44 @@ echo "🚀 開始編譯: ${TARGET_FLAVOR}${TARGET_HARDWARE}Debug"
cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
mkdir -p outputs
# 取得 Flavor 參數
RAW_INPUT="$1"
TARGET_FLAVOR="Chengwai"
if [ -n "$RAW_INPUT" ]; then
LOWER_INPUT=$(echo "$RAW_INPUT" | tr '[:upper:]' '[:lower:]')
if [ "$LOWER_INPUT" = "general" ] || [ "$LOWER_INPUT" = "通用" ] || [ "$LOWER_INPUT" = "通用版" ] || [ "$LOWER_INPUT" = "標準" ] || [ "$LOWER_INPUT" = "標準版" ]; then
TARGET_FLAVOR="General"
elif [ "$LOWER_INPUT" = "chengwai" ] || [ "$LOWER_INPUT" = "晟崴" ] || [ "$LOWER_INPUT" = "晟崴版" ]; then
TARGET_FLAVOR="Chengwai"
elif [ "$LOWER_INPUT" = "cmuh" ] || [ "$LOWER_INPUT" = "中國醫" ] || [ "$LOWER_INPUT" = "中國醫版" ]; then
TARGET_FLAVOR="Cmuh"
else
TARGET_FLAVOR=$(echo "$RAW_INPUT" | awk '{print toupper(substr($0,1,1))tolower(substr($0,2))}')
fi
fi
# 根據 Flavor 動態設定智慧硬體規格預設值
TARGET_HARDWARE="S12XYU"
if [ "$TARGET_FLAVOR" = "Cmuh" ]; then
TARGET_HARDWARE="S9XYU"
if [ -n "$RAW_INPUT" ]; then
LOWER=$(echo "$RAW_INPUT" | tr '[:upper:]' '[:lower:]')
case "$LOWER" in
s9|s9xyu|21.5|大螢幕|中國醫|中國醫版|cmuh) TARGET_HARDWARE="S9XYU" ;;
s12|s12xyu|10|晟崴|晟崴版|chengwai|通用|通用版|general) TARGET_HARDWARE="S12XYU" ;;
esac
fi
# 取得手動指定的第二個硬體規格參數 (S9 / S12)
RAW_HW_INPUT="$2"
if [ -n "$RAW_HW_INPUT" ]; then
LOWER_HW=$(echo "$RAW_HW_INPUT" | tr '[:upper:]' '[:lower:]')
if [ "$LOWER_HW" = "s9" ] || [ "$LOWER_HW" = "s9xyu" ] || [ "$LOWER_HW" = "大螢幕" ] || [ "$LOWER_HW" = "21.5" ]; then
TARGET_HARDWARE="S9XYU"
elif [ "$LOWER_HW" = "s12" ] || [ "$LOWER_HW" = "s12xyu" ] || [ "$LOWER_HW" = "10" ]; then
TARGET_HARDWARE="S12XYU"
fi
fi
# 解析硬體規格來決定輸出檔案名格式 (S9 -> S_9, S12 -> S_12)
FORMATTED_HW=""
if [ "$TARGET_HARDWARE" = "S9XYU" ]; then
FORMATTED_HW="S_9_XY_U"
FORMATTED_HW="S_9_XY_U"; MAJOR=21
else
FORMATTED_HW="S_12_XY_U"
FORMATTED_HW="S_12_XY_U"; MAJOR=10
fi
# 動態尋找編譯產出的最新 APK 檔案
APK_SOURCE=$(find app/build/outputs/apk/${TARGET_FLAVOR}${TARGET_HARDWARE}/debug/ -name "app-${FORMATTED_HW}_${TARGET_FLAVOR}-*-debug.apk" | head -n 1)
# 動態尋找編譯產出的最新 APK 檔案(檔名已無 project 段app-<HW>-<ver>-debug.apk
APK_SOURCE=$(find app/build/outputs/apk/${TARGET_HARDWARE}/debug/ -name "app-${FORMATTED_HW}-*-debug.apk" | head -n 1)
if [ -n "$APK_SOURCE" ]; then
APK_NAME=$(basename "$APK_SOURCE")
APK_DEST="outputs/$APK_NAME"
cp "$APK_SOURCE" "$APK_DEST"
echo "✅ APK 已經複製到 outputs 資料夾,路徑為:$APK_DEST"
# 提取與計算版本資訊
MAJOR=$(grep "def verMajor" app/build.gradle | awk '{print $4}')
# 若在 build.gradle 內 Major 是由 hardware 動態判定,直接讀取並在 Shell 依據當前規格計算
if [ "$TARGET_HARDWARE" = "S9XYU" ]; then
MAJOR=21
else
MAJOR=10
fi
MINOR=$(grep "def verMinor" app/build.gradle | awk '{print $4}')
PATCH=$(grep "def verPatch" app/build.gradle | awk '{print $4}')
VERSION_NAME="${MAJOR}_0${MINOR}_${PATCH}_R"
VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH))
FLAVOR="${TARGET_FLAVOR}${TARGET_HARDWARE}"
echo "=================================================="
echo "📢 後台更新 keyin 參考資料:"
echo "--------------------------------------------------"
echo "* 產品風味 (Flavor)$FLAVOR"
echo "* 硬體規格 (hardware)$TARGET_HARDWARE${MAJOR} 吋)"
echo "* 版本名稱 (versionName)$VERSION_NAME"
echo "* 版本代碼 (versionCode)$VERSION_CODE"
echo "* APK 檔案名稱:$APK_NAME"
echo "* 購買流程:由雲端 shopping_mode 決定basic / employee_card / pickup_sheet"
echo "* 更新說明:[請在此處附上口語化的一句話更新說明]"
echo "=================================================="
else
@ -182,54 +124,27 @@ fi
// turbo
ADB_PATH="/mnt/c/Users/User/AppData/Local/Android/Sdk/platform-tools/adb.exe"
# 取得 Flavor 參數
RAW_INPUT="$1"
TARGET_FLAVOR="Chengwai"
if [ -n "$RAW_INPUT" ]; then
LOWER_INPUT=$(echo "$RAW_INPUT" | tr '[:upper:]' '[:lower:]')
if [ "$LOWER_INPUT" = "general" ] || [ "$LOWER_INPUT" = "通用" ] || [ "$LOWER_INPUT" = "通用版" ] || [ "$LOWER_INPUT" = "標準" ] || [ "$LOWER_INPUT" = "標準版" ]; then
TARGET_FLAVOR="General"
elif [ "$LOWER_INPUT" = "chengwai" ] || [ "$LOWER_INPUT" = "晟崴" ] || [ "$LOWER_INPUT" = "晟崴版" ]; then
TARGET_FLAVOR="Chengwai"
elif [ "$LOWER_INPUT" = "cmuh" ] || [ "$LOWER_INPUT" = "中國醫" ] || [ "$LOWER_INPUT" = "中國醫版" ]; then
TARGET_FLAVOR="Cmuh"
else
TARGET_FLAVOR=$(echo "$RAW_INPUT" | awk '{print toupper(substr($0,1,1))tolower(substr($0,2))}')
fi
fi
# 根據 Flavor 動態設定智慧硬體規格預設值
TARGET_HARDWARE="S12XYU"
if [ "$TARGET_FLAVOR" = "Cmuh" ]; then
TARGET_HARDWARE="S9XYU"
if [ -n "$RAW_INPUT" ]; then
LOWER=$(echo "$RAW_INPUT" | tr '[:upper:]' '[:lower:]')
case "$LOWER" in
s9|s9xyu|21.5|大螢幕|中國醫|中國醫版|cmuh) TARGET_HARDWARE="S9XYU" ;;
s12|s12xyu|10|晟崴|晟崴版|chengwai|通用|通用版|general) TARGET_HARDWARE="S12XYU" ;;
esac
fi
# 取得手動指定的第二個硬體規格參數 (S9 / S12)
RAW_HW_INPUT="$2"
if [ -n "$RAW_HW_INPUT" ]; then
LOWER_HW=$(echo "$RAW_HW_INPUT" | tr '[:upper:]' '[:lower:]')
if [ "$LOWER_HW" = "s9" ] || [ "$LOWER_HW" = "s9xyu" ] || [ "$LOWER_HW" = "大螢幕" ] || [ "$LOWER_HW" = "21.5" ]; then
TARGET_HARDWARE="S9XYU"
elif [ "$LOWER_HW" = "s12" ] || [ "$LOWER_HW" = "s12xyu" ] || [ "$LOWER_HW" = "10" ]; then
TARGET_HARDWARE="S12XYU"
fi
fi
# 解析硬體規格來決定輸出檔案名格式 (S9 -> S_9, S12 -> S_12)
FORMATTED_HW=""
if [ "$TARGET_HARDWARE" = "S9XYU" ]; then
FORMATTED_HW="S_9_XY_U"
else
FORMATTED_HW="S_12_XY_U"
fi
# 檢查是否有設備連接
DEVICE_COUNT=$($ADB_PATH devices | grep -v "List of devices" | grep "device" | wc -l)
if [ "$DEVICE_COUNT" -gt 0 ]; then
echo "📱 偵測到 $DEVICE_COUNT 個設備,開始安裝..."
APK_TARGET=$(find outputs/ -name "app-${FORMATTED_HW}_${TARGET_FLAVOR}-*-debug.apk" | head -n 1)
APK_TARGET=$(find outputs/ -name "app-${FORMATTED_HW}-*-debug.apk" | head -n 1)
if [ -n "$APK_TARGET" ]; then
$ADB_PATH install -r -d -t "/home/mama/projects/TaiwanStar_general_size_s_Chengwai/$APK_TARGET"
echo "🚀 安裝完成,正在啟動 App..."

View File

@ -4,19 +4,26 @@ description: 自動編譯正式發行 Release APK、升級 Patch 版本號、並
# 生產環境發版打包工作流 (release-apk)
這個 Workflow 專門為應用程式「生產環境上線/發版」設計。當您準備好將特定客戶的最新功能派送至實體販賣機時,呼叫 `/release-apk` 將自動為您完成嚴格的發行檢查、版號遞增、Release 簽章打包與發版資訊生成。
這個 Workflow 專門為應用程式「生產環境上線/發版」設計。呼叫 `/release-apk` 將自動完成嚴格的發行檢查、版號遞增、Release 簽章打包與發版資訊生成。
> [!IMPORTANT]
> **客戶專案(購買流程)已收斂為執行期 `shopping_mode`(雲端 B014 下發),不再用 flavor 分家。**
> 發版對象只剩 **硬體規格hardware flavor**`S12XYU`10 吋)/ `S9XYU`21.5 吋 / Android 9
> 同一支 APK 上線後,現場機台由雲端 `shopping_mode` 決定購買流程。
> **部署鐵律**發布統一版前star-cloud 必須先為每台現役機設好 `shopping_mode`
> (晟崴=employee_card、中國醫=pickup_sheet否則預設 basic 會跑錯流程。
> [!TIP]
> **支援參數化指定編譯變體:**
> * 預設(不帶參數):打包 **`Chengwai` (晟崴客製正式版)**。
> * 您可以呼叫 `/release-apk General`(或 `general`)打包 **`General` (通用正式版)**。
> * 您可以呼叫 `/release-apk Cmuh`(或 `cmuh`)打包 **`Cmuh` (中國醫客製正式版)**。
> **參數 = 硬體規格**
> * 預設(不帶參數):打包 **`S12XYU`10 吋)**。
> * `/release-apk S9`(或 `S9XYU` / `中國醫`)→ 打包 **`S9XYU`21.5 吋)**。
> * `/release-apk S12`(或 `S12XYU` / `晟崴` / `通用`)→ 打包 **`S12XYU`10 吋)**。
---
## 步驟零:發行檢查與版號自動遞增 (OTA 鐵律)
為了確保實體機台能夠順利觸發 OTA 自動下載覆蓋安裝,新編譯的 `versionCode` 必須嚴格大於現場的舊版號:
1. AI 助手將讀取 `app/build.gradle` 並自動將 `verPatch`(修補版號)**遞增 1**(例如從 `8` 自動升級為 `9`,確保 versionCode 順利變大。
1. AI 助手將讀取 `app/build.gradle` 並自動將 `verPatch`(修補版號)**遞增 1**,確保 versionCode 順利變大。
2. 自動在 terminal 呈報即將打包的最新版本號資訊。
## 步驟一:清理快取並進行正式簽章編譯
@ -40,28 +47,22 @@ fi
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
# 取得目標 Flavor 參數(預設為 Chengwai支援 General, Chengwai, Cmuh
TARGET_FLAVOR="Chengwai"
if [ -n "$1" ]; then
TARGET_FLAVOR=$(echo "$1" | awk '{print toupper(substr($0,1,1))tolower(substr($0,2))}')
# 取得目標硬體規格(預設 S12XYU
RAW_INPUT="$1"
TARGET_HARDWARE="S12XYU"
if [ -n "$RAW_INPUT" ]; then
LOWER=$(echo "$RAW_INPUT" | tr '[:upper:]' '[:lower:]')
case "$LOWER" in
s9|s9xyu|21.5|大螢幕|中國醫|中國醫版|cmuh) TARGET_HARDWARE="S9XYU" ;;
s12|s12xyu|10|晟崴|晟崴版|chengwai|通用|通用版|general) TARGET_HARDWARE="S12XYU" ;;
*) echo "⚠️ 無法識別「$RAW_INPUT」沿用預設 S12XYU" ;;
esac
fi
# 安全防護:檢查是否為支援的客製項目
if [ "$TARGET_FLAVOR" != "General" ] && [ "$TARGET_FLAVOR" != "Chengwai" ] && [ "$TARGET_FLAVOR" != "Cmuh" ]; then
echo "❌ 不支援的 Flavor: $TARGET_FLAVOR請使用 General, Chengwai 或 Cmuh。"
exit 1
fi
# 根據專案 Flavor 判定硬體 Flavor中國醫 Cmuh 使用 S9XYU其餘使用 S12XYU
HW_FLAVOR="S12XYU"
if [ "$TARGET_FLAVOR" = "Cmuh" ]; then
HW_FLAVOR="S9XYU"
fi
echo "📦 [Release] 開始編譯正式版變體: ${TARGET_FLAVOR}${HW_FLAVOR}Release"
echo "📦 [Release] 開始編譯正式版變體: assemble${TARGET_HARDWARE}Release"
# 使用 clean 確保完整重新編譯,避開 incremental 快取問題
./gradlew clean :app:assemble${TARGET_FLAVOR}${HW_FLAVOR}Release --no-daemon
./gradlew clean :app:assemble${TARGET_HARDWARE}Release --no-daemon
```
## 步驟二:導出正式 APK 並產出後台 Keyin 派送參考資料
@ -72,19 +73,24 @@ echo "📦 [Release] 開始編譯正式版變體: ${TARGET_FLAVOR}${HW_FLAVOR}Re
cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
mkdir -p outputs
# 取得 Flavor 參數
TARGET_FLAVOR="Chengwai"
if [ -n "$1" ]; then
TARGET_FLAVOR=$(echo "$1" | awk '{print toupper(substr($0,1,1))tolower(substr($0,2))}')
RAW_INPUT="$1"
TARGET_HARDWARE="S12XYU"
if [ -n "$RAW_INPUT" ]; then
LOWER=$(echo "$RAW_INPUT" | tr '[:upper:]' '[:lower:]')
case "$LOWER" in
s9|s9xyu|21.5|大螢幕|中國醫|中國醫版|cmuh) TARGET_HARDWARE="S9XYU" ;;
s12|s12xyu|10|晟崴|晟崴版|chengwai|通用|通用版|general) TARGET_HARDWARE="S12XYU" ;;
esac
fi
HW_FLAVOR="S12XYU"
if [ "$TARGET_FLAVOR" = "Cmuh" ]; then
HW_FLAVOR="S9XYU"
if [ "$TARGET_HARDWARE" = "S9XYU" ]; then
FORMATTED_HW="S_9_XY_U"; MAJOR=21
else
FORMATTED_HW="S_12_XY_U"; MAJOR=10
fi
# 動態尋找編譯產出的最新正式版 APK 檔案
APK_SOURCE=$(find app/build/outputs/apk/${TARGET_FLAVOR}${HW_FLAVOR}/release/ -name "app-S_*_${TARGET_FLAVOR}-*-release.apk" | head -n 1)
# 動態尋找編譯產出的最新正式版 APK 檔案(檔名已無 project 段)
APK_SOURCE=$(find app/build/outputs/apk/${TARGET_HARDWARE}/release/ -name "app-${FORMATTED_HW}-*-release.apk" | head -n 1)
if [ -n "$APK_SOURCE" ]; then
APK_NAME=$(basename "$APK_SOURCE")
@ -93,31 +99,21 @@ if [ -n "$APK_SOURCE" ]; then
echo "=================================================="
echo "✅ 正式版 APK 封裝成功,已導出至:$APK_DEST"
echo "=================================================="
# 提取與計算版本資訊
MINOR=$(grep "def verMinor" app/build.gradle | awk '{print $4}')
PATCH=$(grep "def verPatch" app/build.gradle | awk '{print $4}')
# 根據硬體規格決定 Major (S9XYU 為 21 吋S12XYU 為 10 吋)
if [ "$HW_FLAVOR" = "S9XYU" ]; then
MAJOR=21
else
MAJOR=10
fi
VERSION_NAME="${MAJOR}_0${MINOR}_${PATCH}_R"
VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH))
FLAVOR="${TARGET_FLAVOR}${HW_FLAVOR}"
echo "🚀 實體販賣機 OTA 後台新增版本 Keyin 參考資料:"
echo "--------------------------------------------------"
echo "1. 專案/客戶風味 (Flavor) : $FLAVOR"
echo "1. 硬體規格 (hardware) : $TARGET_HARDWARE${MAJOR} 吋)"
echo "2. 版本代碼 (versionCode) : $VERSION_CODE"
echo "3. 版本名稱 (versionName) : $VERSION_NAME"
echo "4. 螢幕吋數 (Screen Size) : ${MAJOR} 吋"
echo "5. 正式版 APK 檔名 : $APK_NAME"
echo "4. 正式版 APK 檔名 : $APK_NAME"
echo "5. 購買流程 : 由雲端 shopping_mode 決定(須先於後台設好)"
echo "--------------------------------------------------"
echo "📢 [溫馨提醒] 請將此 APK 上傳至 OTA 平台,並設定為對應機型的強迫升級。"
echo "📢 [溫馨提醒] 請將此 APK 上傳至 OTA 平台(依硬體分版),並確認該機 shopping_mode 已設定。"
echo "=================================================="
else
echo "❌ 找不到編譯產出的正式版 APK 檔案,封裝失敗!"
@ -126,14 +122,13 @@ fi
```
## 步驟三:自動化 Git 發版標記引導
為了落實公司發版規範第 5 條「必須在主分支合併節點打上 Git Tag以利日後追溯特定現場機台的版本程式碼」AI 助手會自動為您計算出對應的版本 Tag 標籤與提示:
為了落實公司發版規範第 5 條「必須在主分支合併節點打上 Git Tag以利日後追溯特定現場機台的版本程式碼」AI 助手會自動計算出對應的版本 Tag 標籤與提示:
```bash
// turbo
MAJOR=$(grep "def verMajor" app/build.gradle | awk '{print $4}')
MINOR=$(grep "def verMinor" app/build.gradle | awk '{print $4}')
PATCH=$(grep "def verPatch" app/build.gradle | awk '{print $4}')
TAG_NAME="v${MAJOR}.${MINOR}.${PATCH}"
TAG_NAME="v1.${MINOR}.${PATCH}"
echo "=================================================="
echo "🔔 Git 發版追溯標記指南"

View File

@ -1,22 +1,25 @@
---
description: 編譯 Debug APK、導出 outputs/、偵測到設備則安裝(晟崴/通用/中國醫,可選硬體尺寸
argument-hint: [General|Chengwai|Cmuh] [S9|S12]
description: 編譯 Debug APK、導出 outputs/、偵測到設備則安裝(依硬體規格 S9/S12 打包;購買流程由雲端 shopping_mode 決定
argument-hint: [S9|S12]
allowed-tools: Bash, Read, Edit
---
執行本專案的 APK 編譯安裝 workflow定義於 `.agents/workflows/compiler-apk.md`(單一真相來源)。
本次參數:「$ARGUMENTS」第一個=Flavor第二個=硬體 S9/S12可能為空
本次參數:「$ARGUMENTS」硬體規格S9 / S12可能為空預設 S12XYU
> 注意:客戶專案(購買流程)已收斂為執行期 `shopping_mode`(雲端 B014 下發),打包對象只剩硬體規格。
> 同一支 APK 在現場依雲端 `shopping_mode` 切換 basic / employee_card / pickup_sheet。
## 執行方式
1. 先 Read `.agents/workflows/compiler-apk.md`,理解步驟零~三。
2. 步驟零(版號):執行 `git status --porcelain app/`;若有未提交改動且本輪對話尚未升過版,讀 `app/build.gradle``verPatch` 遞增 1 並回報新版號。
3. 步驟一~三:依序執行 workflow 內每個 bash 區塊。**關鍵**workflow 的 bash 以位置參數取得 Flavor 與硬體,所以執行每個區塊前,先在區塊最前面注入本次參數,例如
3. 步驟一~三:依序執行 workflow 內每個 bash 區塊。**關鍵**workflow 的 bash 以位置參數 `$1` 取得硬體規格,所以執行每個區塊前,先在區塊最前面注入本次參數:
set -- $ARGUMENTS
讓 workflow 內的位置參數正確解析(第一個=Flavor、第二個=S9/S12。若本次無參數workflow 自身預設 Chengwai智慧硬體
讓 workflow 內的 `$1` 正確解析S9/S12或相容舊用語 中國醫→S9、晟崴/通用→S12。若本次無參數workflow 自身預設 S12XYU
4. 環境用 workflow 指定的 Java 17`JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64`)。
5. 編譯/導出屬長任務 → 用 run_in_background完成再回報。
6. 步驟三會偵測 adb 設備:有連設備才安裝並啟動 App沒設備就保留 APK 結束。
7. 最後完整回報 workflow 輸出的「後台 keyin 參考資料」(Flavor / versionName / versionCode / APK 檔名)。
7. 最後完整回報 workflow 輸出的「後台 keyin 參考資料」(硬體規格 / versionName / versionCode / APK 檔名)。

View File

@ -1,21 +1,24 @@
---
description: 正式版 Release APK 打包、升 Patch 版號、產出 OTA keyin 與 Git tag 指引(晟崴/通用/中國醫
argument-hint: [General|Chengwai|Cmuh]
description: 正式版 Release APK 打包、升 Patch 版號、產出 OTA keyin 與 Git tag 指引(依硬體 S9/S12流程由雲端 shopping_mode 決定
argument-hint: [S9|S12]
allowed-tools: Bash, Read, Edit
---
執行本專案的正式發版 workflow定義於 `.agents/workflows/release-apk.md`(單一真相來源)。
本次參數:「$ARGUMENTS」Flavor可能為空預設 Chengwai
本次參數:「$ARGUMENTS」硬體規格S9 / S12可能為空預設 S12XYU
> 注意:客戶專案(購買流程)已收斂為執行期 `shopping_mode`(雲端 B014 下發),發版對象只剩硬體規格。
> 部署鐵律:發布前 star-cloud 必須先為每台現役機設好 `shopping_mode`,否則預設 basic 跑錯流程。
## 執行方式
1. 先 Read `.agents/workflows/release-apk.md`,理解步驟零~三。
2. 步驟零OTA 鐵律):讀 `app/build.gradle``verPatch` 遞增 1確保 versionCode 變大可觸發 OTA回報新版號。
3. 步驟一~三:依序執行 workflow 內每個 bash 區塊。**關鍵**workflow 的 bash 以位置參數取得 Flavor,執行每個區塊前先注入本次參數:
3. 步驟一~三:依序執行 workflow 內每個 bash 區塊。**關鍵**workflow 的 bash 以位置參數 `$1` 取得硬體規格,執行每個區塊前先注入本次參數:
set -- $ARGUMENTS
無參數時預設 Chengwai。硬體Cmuh→S9XYU其餘→S12XYU
無參數時預設 S12XYU接受 S9/S12或相容 中國醫→S9、晟崴/通用→S12
4. 環境用 Java 17`JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64`Release 會跑混淆與 lintVital較久 → 用 run_in_background。
5. 步驟二導出正式簽章 APK 到 `outputs/` 並印出 OTA 後台 keyin 對照資料。
6. 步驟三回報 Git 發版 tag 指引。

View File

@ -10,7 +10,7 @@ plugins {
//
// =========================================================================
def verMinor = 1
def verPatch = 69
def verPatch = 70
@ -30,23 +30,13 @@ android {
}
// =====================================================================
// Flavorproject× hardware
// Flavorhardware / IPC
// shopping_mode B014 flavor
// hardware minSdk COM port ComPort.getFilepath
// =====================================================================
flavorDimensions "project", "hardware"
flavorDimensions "hardware"
productFlavors {
General {
dimension "project"
}
Chengwai {
dimension "project"
}
Cmuh {
dimension "project"
}
S12XYU {
dimension "hardware"
}
@ -68,8 +58,7 @@ android {
//
// _安卓號_主板號_機器系列_專案名稱_螢幕大小_次版號_修補版號_環境
applicationVariants.all { variant ->
def project = variant.productFlavors[0].name // project dimension
def hardware = variant.productFlavors[1].name // hardware dimension
def hardware = variant.productFlavors[0].name // hardware dimension
def buildType = variant.buildType.name
// (verMajor)
@ -100,8 +89,8 @@ android {
def hwFormatted = hardware
.replaceAll(/^([A-Z])(\d+)([A-Z]+)([A-Z])$/, '$1_$2_$3_$4')
// app-S_12_XY_U_Chengwai-10_08_8_R-release.apk
output.outputFileName = "app-${hwFormatted}_${project}-${calculatedVersionName}-${buildType}.apk"
// app-S_12_XY_U-10_08_8_R-release.apk shopping_mode
output.outputFileName = "app-${hwFormatted}-${calculatedVersionName}-${buildType}.apk"
}
}
@ -152,7 +141,7 @@ dependencies {
implementation 'io.netty:netty-handler:4.1.94.Final'
implementation 'com.google.code.gson:gson:2.10.1'
// Kotlin / MVVM General flavor
// Kotlin / MVVMbasic
implementation libs.kotlin.stdlib
implementation libs.lifecycle.viewmodel.ktx
implementation libs.lifecycle.livedata.ktx

View File

@ -1,57 +0,0 @@
package com.unibuy.smartdevice.ui;
import android.content.Context;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.ui.dialog.MemberCardPickupDialog;
/**
* Chengwai (晟崴版) 銷售流程處理器
* 點選商品/購物車時直接進入員工卡刷卡取貨 Dialog (MemberCardPickupDialog)
*/
public class SaleFlowHandler {
protected final Context context;
protected final SaleFlowCallback callback;
protected final HandlerMain handlerMain;
public SaleFlowHandler(Context context, HandlerMain handlerMain, SaleFlowCallback callback) {
this.context = context;
this.handlerMain = handlerMain;
this.callback = callback;
}
public void onSetupUI() {
// 晟崴版 UI 設定預設無動作
}
public void onSetupUI(android.view.View rootView) {
// 晟崴版 UI 設定預設無動作
}
public void onBuyRequested() {
new MemberCardPickupDialog(context, handlerMain).show();
}
public void onShoppingCartRequested() {
// 晟崴版維持原行為購物車浮鈕等同進入購買流程
onBuyRequested();
}
/** 晟崴版不消化商品點擊,交回 adapter 既有流程(員工卡取貨)。 */
public boolean handleProductSelected(com.unibuy.smartdevice.structure.SlotStructure slot) {
return false;
}
/** 晟崴版使用標準現金 Dialog。 */
public Class<? extends com.unibuy.smartdevice.DialogAbstract> cashDialogClass() {
return com.unibuy.smartdevice.ui.dialog.CashPayDialog.class;
}
public void onBarcodeReceived(String rawData) {
// 晟崴版不處理掃碼
}
public void onPickupRequested() {
// 晟崴版不處理取貨碼
}
}

View File

@ -1,591 +0,0 @@
package com.unibuy.smartdevice.ui.dialog;
import android.content.Context;
import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.databinding.DialogAccessCodeBinding;
import com.unibuy.smartdevice.databinding.DialogPickupCodeFontendBinding;
import com.unibuy.smartdevice.devices.DeviceType;
import com.unibuy.smartdevice.devices.SlotField;
import com.unibuy.smartdevice.devices.UsbDev;
import com.unibuy.smartdevice.exception.LogsEmptyException;
import com.unibuy.smartdevice.exception.LogsIOException;
import com.unibuy.smartdevice.exception.LogsParseException;
import com.unibuy.smartdevice.exception.LogsSettingEmptyException;
import com.unibuy.smartdevice.exception.LogsUnsupportedOperationException;
import com.unibuy.smartdevice.external.HttpAPI;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.structure.ProductStructure;
import com.unibuy.smartdevice.structure.SlotStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.HandlerMainCountdown;
import com.unibuy.smartdevice.tools.HandlerMainScheduler;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
import com.unibuy.smartdevice.tools.Tools;
import com.unibuy.smartdevice.ui.FontendActivity;
import android.app.AlertDialog;
import android.os.Handler;
import android.os.Looper;
import com.unibuy.smartdevice.controller.DevController;
import com.unibuy.smartdevice.devices.PortTools;
import com.unibuy.smartdevice.ui.dialog.BuyDialog;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class FontendPickupCodeDialog extends DialogAbstract {
public enum Option {
TOAST(0),
COUNTDOWN_CANCEL(1),
SET_COUNTDOWN_TEXT(2),
INVOICE_SUCCESSFUL(4),
INVOICE_FAILED(5),
CHANGE_DISPATCH(6),
CHANGE_BUY(7),
SET_STATUS_TEXT(8),
;
private int option;
public int getOption() {
return option;
}
Option(int option) {
this.option = option;
}
}
public static final Map<Integer, Option> optionMap = new HashMap<>();
static {
for (Option option : Option.values()) {
optionMap.put(option.getOption(), option);
}
}
@Override
protected Context setCtx() {
return getContext();
}
@Override
protected Class<? extends DialogAbstract> setCls() {
return getClass();
}
@Override
protected HandlerMain setHandlerMain() {
return new ToastHandlerMain(getCtx(), getLogs()) {
@Override
protected void execute(Context context, int commandCode, String message) {
Option option = optionMap.get(commandCode);
switch (option) {
case TOAST:
super.execute(context, commandCode, message);
break;
case COUNTDOWN_CANCEL:
cancelOnThreadCountdown();
break;
case SET_COUNTDOWN_TEXT:
setButtonCancelText(Integer.valueOf(message));
break;
case INVOICE_SUCCESSFUL:
MyApp.getInstance().getInvoiceData().setCarrier(message);
MyApp.getInstance().getReportFlowInfoData().setInvoiceInfo("carrier:" + MyApp.getInstance().getInvoiceData().getCarrier());
break;
case INVOICE_FAILED:
break;
case CHANGE_DISPATCH:
//reportAccessCodeData();//上傳通行碼資訊到後台系統
changeDispatch();
break;
case CHANGE_BUY:
try {
getLogs().info("CHANGE_BUY triggered with message: " + message);
SlotStructure slotProduct = MyApp.getInstance().getSlotDataByPid(message);
if (slotProduct == null) {
setStatusText(getContext().getString(R.string.product_not_found));
return;
}
getLogs().info("Slot found: " + slotProduct.getSlot() + ", Field: " + slotProduct.getField());
SlotField slotField = SlotField.getSlotField(slotProduct.getField());
switch (slotField) {
case VMC:
if (slotProduct.isLock()) {
getSrcHandlerMain().start("FontendPickupCodeDialog", "@"+getContext().getString(R.string.product_sales_suspended));//商品暫停販售
dialogCancel();
} else if (slotProduct.getCount() == 0) {
getSrcHandlerMain().start("FontendPickupCodeDialog", "@"+getContext().getString(R.string.product_out_of_stock));//商品無存貨
dialogCancel();
} else {
// 取貨口檢查已在 onCreate 執行這裡直接進行
BuyStructure buyProduct = new BuyStructure(slotProduct.getField(), slotProduct.getSlot(), slotProduct.getProduct(), 1);
MyApp.getInstance().getBuyList().add(buyProduct);
changeBuy();
}
break;
case ELECTRIC:
if (slotProduct.isLock())
MyApp.getInstance().getSlotDataByPid(message).setLock(false);
getSrcHandlerMain().start("FontendPickupCodeDialog", "@"+getContext().getString(R.string.unlock) + slotProduct.getProduct().getProductName());//解鎖
getSrcHandlerMain().start("FontendPickupCodeDialog", FontendActivity.Option.RESET_DATA.getOption(), "reset data");
dialogCancel();
break;
}
} catch (LogsEmptyException e) {
setStatusText(getContext().getString(R.string.product_not_found));//找不到商品
} catch (Exception e) {
getLogs().info("Error in CHANGE_BUY: " + e.getMessage());
e.printStackTrace();
// 發生未知錯誤時嘗試繼續或顯示錯誤
setStatusText("System Error: " + e.getMessage());
}
break;
case SET_STATUS_TEXT:
setStatusText(message);
break;
}
}
};
}
private DialogPickupCodeFontendBinding binding;
private CloseDialogOnThreadHandler closeDialogCountdown;
private boolean isChangeDispatch;
private boolean openBarcode;
private HttpAPI httpAPI;
private boolean haveThings, doorError;
public FontendPickupCodeDialog(Context context, HandlerMain srcHandlerMain) {
super(context, srcHandlerMain);
}
@Override
protected void onCreate(Context context) {
binding = DialogPickupCodeFontendBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
MyApp.getInstance().initialReportData();
isChangeDispatch = true;
openBarcode = true;
httpAPI = new HttpAPI(getHandlerMain());
binding.buttonFinish.setOnClickListener(v -> changeOk());
binding.buttonCancel.setOnClickListener(v -> changeBuy());
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
closeDialogCountdown.start(40);
binding.buttonR1.setOnClickListener(v -> {
String text = binding.editTextNumber.getText().toString();
if (text.length() > 0) { // 避免長度為 0 崩潰
binding.editTextNumber.setText(text.substring(0, text.length() - 1));
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
}
closeDialogCountdown.setCountdown(40);
});
binding.buttonC1.setOnClickListener(v -> {
binding.editTextNumber.setText("");
closeDialogCountdown.setCountdown(40);
});
binding.button10.setOnClickListener(v -> {
String btnName = binding.button10.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button11.setOnClickListener(v -> {
String btnName = binding.button11.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button12.setOnClickListener(v -> {
String btnName = binding.button12.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button13.setOnClickListener(v -> {
String btnName = binding.button13.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button14.setOnClickListener(v -> {
String btnName = binding.button14.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button15.setOnClickListener(v -> {
String btnName = binding.button15.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button16.setOnClickListener(v -> {
String btnName = binding.button16.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button17.setOnClickListener(v -> {
String btnName = binding.button17.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button18.setOnClickListener(v -> {
String btnName = binding.button18.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button19.setOnClickListener(v -> {
String btnName = binding.button19.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
new ReadBarCodeOnScheduler(getHandlerMain()).start(5, TimeUnit.SECONDS);
// 進入頁面時立即檢查取貨口 (使用貨道1作為代表)
// callback null表示檢查通過後不做額外動作僅在檢查失敗時顯示警告
checkSlotHaveProduct(1, 1, null);
}
public class ReadBarCodeOnScheduler extends HandlerMainScheduler {
public ReadBarCodeOnScheduler(HandlerMain handlerMain) {
super(handlerMain);
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected void execute(HandlerMain handlerMain) {
try {
int totalPrice = 0;
for (BuyStructure buy : MyApp.getInstance().getBuyList()) {
totalPrice += buy.getFullPrice();
}
UsbDev usbDev = new UsbDev(DeviceType.QRCODE_SCANNER);
byte[] buffer = new byte[1000];
usbDev.read(buffer, 100);
for (int i=0; i<7; i++) {
if (isRun() && openBarcode) {
int size = usbDev.read(buffer, 5000);
if (size > 5) {
String barCodeString = new String(buffer, 0, size);
getHandlerMain().start(getLogs().getClassName(), getCtx().getString(R.string.receive_barcode)+""+ Tools.randomReplace(barCodeString, 0.3)+""+getCtx().getString(R.string.send_transaction));//收到條碼,送出交易
try {
MyApp.getInstance().getMemberVerificationStructure().setProductId("0");
MyApp.getInstance().getMemberVerificationStructure().setProductPrice(0);
MyApp.getInstance().getMemberVerificationStructure().setMemberBarCode(barCodeString);
MyApp.getInstance().getMemberVerificationStructure().setVerificationType(2);
httpAPI.memberVerification(MyApp.getInstance().getMemberVerificationStructure());
} catch (LogsParseException | LogsSettingEmptyException e) {
getLogs().warning(e);
}
openBarcode = false;
}
}
}
usbDev.closePort();
} catch (LogsUnsupportedOperationException | LogsIOException | LogsEmptyException e) {
getLogs().warning(e);
}
}
@Override
protected Class<?> setCls() {
return getClass();
}
}
public int getTotalPrice() {
int totalPrice = 0;
if (MyApp.getInstance().getBuyList().size() == 0) return totalPrice;
for(BuyStructure buy: MyApp.getInstance().getBuyList()) {
totalPrice += buy.getFullPrice();
}
return totalPrice;
}
public void changeDispatch() {
if (isChangeDispatch) {
closeDialogCountdown.shutdown();
isChangeDispatch = false;
changeDialog(DispatchDialog.class);
}
}
public void changeOk() {
try {
String number = binding.editTextNumber.getText().toString();
// 長度檢查
if (number.length() < 8) {
getHandlerMain().start(getClass().getSimpleName(), "統一編號長度不能少於 8 碼");
return;
}
MyApp.getInstance().getMemberVerificationStructure().setProductId("0");
MyApp.getInstance().getMemberVerificationStructure().setProductPrice(0);
MyApp.getInstance().getMemberVerificationStructure().setMemberBarCode(number);
MyApp.getInstance().getMemberVerificationStructure().setVerificationType(2);
httpAPI.memberVerification(MyApp.getInstance().getMemberVerificationStructure());
} catch (LogsParseException e) {
throw new RuntimeException(e);
} catch (LogsSettingEmptyException e) {
throw new RuntimeException(e);
}
}
public void changeBuy() {
closeDialogCountdown.shutdown();
BuyPickupCodeDialog buyDialog = new BuyPickupCodeDialog(getContext(), getSrcHandlerMain());
buyDialog.show();
cancel();
}
public void dialogCancel() {
closeDialogCountdown.shutdown();
MyApp.getInstance().getBuyList().clear();
cancel();
}
public void cancelOnThreadCountdown() {
MyApp.getInstance().getBuyList().clear();
cancel();
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText(getContext().getString(R.string.return_to)+"("+countdown+")");//返回
}
public void setStatusText(String message) {
binding.textStatus.setText(message);
}
public void reportAccessCodeData() {
try {
MyApp.getInstance().getReportAccessCodeStructure().setField(MyApp.getInstance().getReportAccessCodeStructure().getField());
MyApp.getInstance().getReportAccessCodeStructure().setSlot(MyApp.getInstance().getBuyList().get(0).getSlot());
MyApp.getInstance().getReportAccessCodeStructure().setProductId(MyApp.getInstance().getReportAccessCodeStructure().getProductId());
httpAPI.reportAccessCode(MyApp.getInstance().getReportAccessCodeStructure());
} catch (LogsParseException e) {
throw new RuntimeException(e);
} catch (LogsSettingEmptyException e) {
throw new RuntimeException(e);
}
}
public void updatePickupCodeData() {
try {
String number = binding.editTextNumber.getText().toString();
MyApp.getInstance().getMemberVerificationStructure().setProductId("0");
MyApp.getInstance().getMemberVerificationStructure().setProductPrice(0);
MyApp.getInstance().getMemberVerificationStructure().setMemberBarCode(number);
MyApp.getInstance().getMemberVerificationStructure().setVerificationType(2);
httpAPI.memberVerification(MyApp.getInstance().getMemberVerificationStructure());
} catch (LogsParseException e) {
throw new RuntimeException(e);
} catch (LogsSettingEmptyException e) {
throw new RuntimeException(e);
}
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {
public CloseDialogOnThreadHandler(HandlerMain handlerMain) {
super(handlerMain);
handlerMain.start(getClass().getSimpleName(), CashPayDialog.Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(getSrcCountdown()));
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected void close(HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.COUNTDOWN_CANCEL.getOption(), "close dialog");
}
@Override
protected void execute(long countdown, HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(countdown));
}
@Override
protected Class<?> setCls() {
return getClass();
}
}
public void checkSlotHaveProduct(int slotnum, int slotcount, Runnable onSafe) {
haveThings = false;
doorError = false;
// 使用 AtomicBoolean 防止重複執行 (Timeout vs Callback)
// 預設為 false只要有一方執行過就設為 true避免重複觸發
java.util.concurrent.atomic.AtomicBoolean isHandled = new java.util.concurrent.atomic.AtomicBoolean(false);
// 設定 3000ms 超時機制如果機器沒反應視為正常並繼續
// 增加這段是因為如果 Slot 為空VMC 可能不會回傳任何資料導致程式卡住
// 但如果 onSafe null (代表僅監控不阻塞流程)則不啟動超時以免太快判定為安全而蓋過隨後回傳的 Error
if (onSafe != null) {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (!isHandled.getAndSet(true)) {
getLogs().info("VMC Check Slot Timeout - Approving Safe (Timeout Safeguard)");
onSafe.run();
}
}, 3000);
}
MyApp.getInstance().getDevXinYuanController().addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlotPre(slotnum,slotcount));
MyApp.getInstance().getDevXinYuanController().addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slotnum));
MyApp.getInstance().getDevXinYuanController().getReadBufferByNow(new DevCheckSlotByVMC(getHandlerMain(), onSafe, isHandled));
}
public class DevCheckSlotByVMC extends DevController.ReadBufferOnScheduler {
private Runnable onSafe;
private java.util.concurrent.atomic.AtomicBoolean isHandled;
public DevCheckSlotByVMC(HandlerMain handlerMain, Runnable onSafe, java.util.concurrent.atomic.AtomicBoolean isHandled) {
super(handlerMain);
this.onSafe = onSafe;
this.isHandled = isHandled;
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected Class<?> setCls() {
return getClass();
}
@Override
public void readBufferOnScheduler(DevController devController, HandlerMain handlerMain) {
// 每次檢查前先重置門狀態旗標
doorError = false;
for (byte[] buffer: devController.getReadBufferByMax()) {
if (buffer[0] == 0x02) {
getLogs().info("XinYuan Read:" + PortTools.showHex(buffer));
if (buffer[1] == 0x05) {
haveThings = true;
break;
}else if (buffer[1] == 0x06 || buffer[1] == 0x09) {
// 0x06取貨口門沒關上
// 0x09微波爐取貨口門關閉錯誤
getLogs().info("檢測到取貨口門未正常關閉,狀態碼=" + String.format("%02X", buffer[1]));
doorError = true;
break;
}
}
}
if(haveThings){
// 防止 Timeout 後又執行這裡或者重複執行
if (isHandled.getAndSet(true)) return;
// 使用主執行緒顯示 AlertDialog
new Handler(Looper.getMainLooper()).post(() -> {
AlertDialog dialog = new AlertDialog.Builder(getContext())
.setTitle(R.string.abnormal_reminder)
.setMessage(R.string.there_is_something_at_the_pickup_port)
.setCancelable(false)
.setPositiveButton(R.string.i_knew, (d, which) -> {
d.dismiss();
// 如果有東西可能需要重置或關閉
FontendPickupCodeDialog.this.dialogCancel();
})
.create();
dialog.show();
// 加入 10 秒後自動觸發點擊確定
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (dialog.isShowing()) {
dialog.dismiss();
FontendPickupCodeDialog.this.dialogCancel();
}
}, 10_000); // 10 10000 毫秒
});
} else if (doorError) {
if (isHandled.getAndSet(true)) return;
// 取貨口門未關閉提示使用者並阻止後續購買流程
handlerMain.start("FontendPickupCodeDialog", "取貨口門未正常關閉,請先確認門已關好");
// 使用主執行緒顯示 AlertDialog
new Handler(Looper.getMainLooper()).post(() -> {
AlertDialog dialog = new AlertDialog.Builder(getContext())
.setTitle(R.string.abnormal_reminder)
.setMessage("取貨口門未正常關閉,請先確認門已關好。")
.setCancelable(false)
.setPositiveButton(R.string.i_knew, (d, which) -> {
d.dismiss();
FontendPickupCodeDialog.this.dialogCancel();
})
.create();
dialog.show();
// 加入 10 秒後自動觸發點擊確定
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (dialog.isShowing()) {
dialog.dismiss();
FontendPickupCodeDialog.this.dialogCancel();
}
}, 10_000); // 10 10000 毫秒
});
} else {
// 沒有其他狀況執行後續動作
// 注意這裡假設 VMC 回傳就代表檢測完成如果 VMC 會分段回傳可能需要更嚴謹的判斷
// 但依照原邏輯只要沒 error 就算 pass
if (!isHandled.getAndSet(true)) {
if (onSafe != null) {
new Handler(Looper.getMainLooper()).post(onSafe);
}
}
}
}
}
}

View File

@ -1,263 +0,0 @@
package com.unibuy.smartdevice.ui.dialog;
import android.content.Context;
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.databinding.DialogPickupCodeBuyBinding;
import com.unibuy.smartdevice.devices.SlotField;
import com.unibuy.smartdevice.exception.LogsEmptyException;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.HandlerMainCountdown;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
import com.unibuy.smartdevice.tools.Tools;
import com.unibuy.smartdevice.ui.recycler.RecyclerDialogPickupCodeBuyListAdpter;
import java.util.HashMap;
import java.util.Map;
public class BuyPickupCodeDialog extends DialogAbstract {
public enum Option {
TOAST(0),
COUNTDOWN_CANCEL(1),
SET_COUNTDOWN_TEXT(2),
SET_PRICE(3),
RESET_COUNTDOWM(4),
RESET_COUNTDOWM_TOAST(5),
;
private int option;
public int getOption() {
return option;
}
Option(int option) {
this.option = option;
}
}
public static final Map<Integer, Option> optionMap = new HashMap<>();
static {
for (Option option : Option.values()) {
optionMap.put(option.getOption(), option);
}
}
@Override
protected Context setCtx() {
return getContext();
}
@Override
protected Class<? extends DialogAbstract> setCls() {
return getClass();
}
@Override
protected HandlerMain setHandlerMain() {
return new ToastHandlerMain(getContext(), getLogs()) {
@Override
protected void execute(Context context, int commandCode, String message) {
if (MyApp.getInstance().getBuyList().size() <= 0) {
dialogCancel();
} else {
Option option = optionMap.get(commandCode);
switch (option) {
case TOAST:
super.execute(context, commandCode, message);
break;
case COUNTDOWN_CANCEL:
cancelOnThreadCountdown();
break;
case SET_COUNTDOWN_TEXT:
setButtonCancelText(Integer.valueOf(message));
break;
case SET_PRICE:
binding.textPrice.setText(String.valueOf(getTotalPrice()));
closeDialogCountdown.setCountdown(40);
break;
case RESET_COUNTDOWM:
closeDialogCountdown.setCountdown(40);
break;
case RESET_COUNTDOWM_TOAST:
super.execute(context, commandCode, message);
closeDialogCountdown.setCountdown(40);
break;
}
}
}
};
}
private DialogPickupCodeBuyBinding binding;
private RecyclerDialogPickupCodeBuyListAdpter recyclerDialogPickupCodeBuyListAdpter;
private CloseDialogOnThreadHandler closeDialogCountdown;
private boolean isChangeDispatch;
public BuyPickupCodeDialog(Context context, HandlerMain srcHandlerMain) {
super(context, srcHandlerMain);
getLogs().info("open:" + getClass().getSimpleName());
}
@Override
protected void onCreate(Context context) {
binding = DialogPickupCodeBuyBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
isChangeDispatch = true;
this.recyclerDialogPickupCodeBuyListAdpter = new RecyclerDialogPickupCodeBuyListAdpter(getCtx(), MyApp.getInstance().getBuyList(), getHandlerMain());
this.binding.recyclerDialogBuyList.setAdapter(recyclerDialogPickupCodeBuyListAdpter);
this.binding.recyclerDialogBuyList.setLayoutManager(new LinearLayoutManager(context));
MyApp.getInstance().getReportFlowInfoData().setOrderId(Tools.generateTimeBasedUUID());
MyApp.getInstance().getReportFlowInfoData().setFlowType(6);
MyApp.getInstance().getPostInvoiceByGreenData().setOrderId(MyApp.getInstance().getReportFlowInfoData().getOrderId());
this.binding.layoutMemberTransactions.setVisibility(View.GONE);
this.binding.buttonGetProduct.setOnClickListener(v -> gotoGetProduct());
this.binding.buttonCancel.setOnClickListener(v -> dialogCancel());
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
closeDialogCountdown.start(40);
binding.textPrice.setText(String.valueOf(getTotalPrice()));
}
public int getTotalPrice() {
int totalPrice = 0;
if (MyApp.getInstance().getBuyList().size() == 0) return totalPrice;
for(BuyStructure buy: MyApp.getInstance().getBuyList()) {
totalPrice += buy.getFullPrice();
}
return totalPrice;
}
public void gotoGetProduct() {
if (isChangeDispatch) {
closeDialogCountdown.shutdown();
isChangeDispatch = false;
changeDialog(DispatchDialog.class);
cancel();
}
}
public void dialogCancel() {
closeDialogCountdown.shutdown();
int buyListSize = MyApp.getInstance().getBuyList().size();
MyApp.getInstance().getBuyList().clear();
if (buyListSize > 0) {
recyclerDialogPickupCodeBuyListAdpter.notifyItemRangeRemoved(0, buyListSize);
}
cancel();
}
public void cancelOnThreadCountdown() {
int buyListSize = MyApp.getInstance().getBuyList().size();
MyApp.getInstance().getBuyList().clear();
if (buyListSize > 0) {
recyclerDialogPickupCodeBuyListAdpter.notifyItemRangeRemoved(0, buyListSize);
}
getTools().dialogClose();
cancel();
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("取消("+countdown+")");
}
public void gotoDialog() {
closeDialogCountdown.shutdown();
int flowType = MyApp.getInstance().getReportFlowInfoData().getFlowType();
if (flowType == 5) {
changeCheckout();
} else {
if (MyApp.getInstance().getDevSet().isShoppingCar()) {
if (MyApp.getInstance().getDevSet().isInvoice()) {
changeDialog(InvoiceBarcodeDialog.class);
} else {
changeCheckout();
}
} else {
if (MyApp.getInstance().getDevSet().isInvoice()) {
BuyStructure buyData = MyApp.getInstance().getBuyList().get(0);
try {
SlotField slotField = SlotField.getSlotField(buyData.getField());
if (slotField.isInvoice()) {
changeDialog(InvoiceBarcodeDialog.class);
} else {
changeCheckout();
}
} catch (LogsEmptyException e) {
changeDialog(InvoiceBarcodeDialog.class);
}
} else {
changeCheckout();
}
}
}
}
public void changeCheckout() {
closeDialogCountdown.shutdown();
int flowType = MyApp.getInstance().getReportFlowInfoData().getFlowType();
switch (flowType) {
case 1:
changeDialog(CheckoutNfcCardDialog.class);
break;
case 2:
changeDialog(CheckoutNfcRechargeDialog.class);
break;
case 3:
changeDialog(CheckoutEsunPayDialog.class);
break;
case 4:
changeDialog(CheckoutNfcPhoneDialog.class);
break;
case 5:
changeDialog(CheckoutAccessCodeDialog.class);
break;
case 9:
changeDialog(CheckoutCashDialog.class);
break;
case 70:
changeDialog(CheckoutLinePayDialog.class);
break;
}
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {
public CloseDialogOnThreadHandler(HandlerMain handlerMain) {
super(handlerMain);
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected void close(HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), Option.COUNTDOWN_CANCEL.getOption(), "close dialog");
}
@Override
protected void execute(long countdown, HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(countdown));
}
@Override
protected Class<?> setCls() {
return getClass();
}
}
}

View File

@ -1,429 +0,0 @@
package com.unibuy.smartdevice.ui.dialog;
import android.content.Context;
import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.databinding.DialogAccessCodeBinding;
import com.unibuy.smartdevice.databinding.DialogPickupCodeFontendBinding;
import com.unibuy.smartdevice.devices.DeviceType;
import com.unibuy.smartdevice.devices.SlotField;
import com.unibuy.smartdevice.devices.UsbDev;
import com.unibuy.smartdevice.exception.LogsEmptyException;
import com.unibuy.smartdevice.exception.LogsIOException;
import com.unibuy.smartdevice.exception.LogsParseException;
import com.unibuy.smartdevice.exception.LogsSettingEmptyException;
import com.unibuy.smartdevice.exception.LogsUnsupportedOperationException;
import com.unibuy.smartdevice.external.HttpAPI;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.structure.ProductStructure;
import com.unibuy.smartdevice.structure.SlotStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.HandlerMainCountdown;
import com.unibuy.smartdevice.tools.HandlerMainScheduler;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
import com.unibuy.smartdevice.tools.Tools;
import com.unibuy.smartdevice.ui.FontendActivity;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class FontendPickupCodeDialog extends DialogAbstract {
public enum Option {
TOAST(0),
COUNTDOWN_CANCEL(1),
SET_COUNTDOWN_TEXT(2),
INVOICE_SUCCESSFUL(4),
INVOICE_FAILED(5),
CHANGE_DISPATCH(6),
CHANGE_BUY(7),
SET_STATUS_TEXT(8),
;
private int option;
public int getOption() {
return option;
}
Option(int option) {
this.option = option;
}
}
public static final Map<Integer, Option> optionMap = new HashMap<>();
static {
for (Option option : Option.values()) {
optionMap.put(option.getOption(), option);
}
}
@Override
protected Context setCtx() {
return getContext();
}
@Override
protected Class<? extends DialogAbstract> setCls() {
return getClass();
}
@Override
protected HandlerMain setHandlerMain() {
return new ToastHandlerMain(getCtx(), getLogs()) {
@Override
protected void execute(Context context, int commandCode, String message) {
Option option = optionMap.get(commandCode);
switch (option) {
case TOAST:
super.execute(context, commandCode, message);
break;
case COUNTDOWN_CANCEL:
cancelOnThreadCountdown();
break;
case SET_COUNTDOWN_TEXT:
setButtonCancelText(Integer.valueOf(message));
break;
case INVOICE_SUCCESSFUL:
MyApp.getInstance().getInvoiceData().setCarrier(message);
MyApp.getInstance().getReportFlowInfoData().setInvoiceInfo("carrier:" + MyApp.getInstance().getInvoiceData().getCarrier());
break;
case INVOICE_FAILED:
break;
case CHANGE_DISPATCH:
reportAccessCodeData();//上傳通行碼資訊到後台系統
changeDispatch();
break;
case CHANGE_BUY:
try {
SlotStructure slotProduct = MyApp.getInstance().getSlotDataByPid(message);
SlotField slotField = SlotField.getSlotField(slotProduct.getField());
switch (slotField) {
case VMC:
if (slotProduct.isLock()) {
getSrcHandlerMain().start("FontendPickupCodeDialog", "@商品暫停販售");
dialogCancel();
} else if (slotProduct.getCount() == 0) {
getSrcHandlerMain().start("FontendPickupCodeDialog", "@商品無存貨");
dialogCancel();
}
BuyStructure buyProduct = new BuyStructure(slotProduct.getField(), slotProduct.getSlot(), slotProduct.getProduct(), 1);
MyApp.getInstance().getBuyList().add(buyProduct);
changeBuy();
break;
case ELECTRIC:
if (slotProduct.isLock())
MyApp.getInstance().getSlotDataByPid(message).setLock(false);
getSrcHandlerMain().start("FontendPickupCodeDialog", "@解鎖" + slotProduct.getProduct().getProductName());
getSrcHandlerMain().start("FontendPickupCodeDialog", FontendActivity.Option.RESET_DATA.getOption(), "reset data");
dialogCancel();
break;
}
} catch (LogsEmptyException e) {
setStatusText("找不到商品");
}
break;
case SET_STATUS_TEXT:
setStatusText(message);
break;
}
}
};
}
private DialogPickupCodeFontendBinding binding;
private CloseDialogOnThreadHandler closeDialogCountdown;
private boolean isChangeDispatch;
private boolean openBarcode;
private HttpAPI httpAPI;
public FontendPickupCodeDialog(Context context, HandlerMain srcHandlerMain) {
super(context, srcHandlerMain);
}
@Override
protected void onCreate(Context context) {
binding = DialogPickupCodeFontendBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
MyApp.getInstance().initialReportData();
isChangeDispatch = true;
openBarcode = true;
httpAPI = new HttpAPI(getHandlerMain());
binding.buttonFinish.setOnClickListener(v -> changeOk());
binding.buttonCancel.setOnClickListener(v -> changeBuy());
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
closeDialogCountdown.start(40);
binding.buttonR1.setOnClickListener(v -> {
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text.substring(0, text.length()-1));
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.buttonC1.setOnClickListener(v -> {
binding.editTextNumber.setText("");
closeDialogCountdown.setCountdown(40);
});
binding.button10.setOnClickListener(v -> {
String btnName = binding.button10.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button11.setOnClickListener(v -> {
String btnName = binding.button11.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button12.setOnClickListener(v -> {
String btnName = binding.button12.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button13.setOnClickListener(v -> {
String btnName = binding.button13.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button14.setOnClickListener(v -> {
String btnName = binding.button14.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button15.setOnClickListener(v -> {
String btnName = binding.button15.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button16.setOnClickListener(v -> {
String btnName = binding.button16.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button17.setOnClickListener(v -> {
String btnName = binding.button17.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button18.setOnClickListener(v -> {
String btnName = binding.button18.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
binding.button19.setOnClickListener(v -> {
String btnName = binding.button19.getText().toString();
String text = binding.editTextNumber.getText().toString();
binding.editTextNumber.setText(text+btnName);
binding.editTextNumber.setSelection(binding.editTextNumber.getText().length());
closeDialogCountdown.setCountdown(40);
});
new ReadBarCodeOnScheduler(getHandlerMain()).start(5, TimeUnit.SECONDS);
}
public class ReadBarCodeOnScheduler extends HandlerMainScheduler {
public ReadBarCodeOnScheduler(HandlerMain handlerMain) {
super(handlerMain);
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected void execute(HandlerMain handlerMain) {
try {
int totalPrice = 0;
for (BuyStructure buy : MyApp.getInstance().getBuyList()) {
totalPrice += buy.getFullPrice();
}
UsbDev usbDev = new UsbDev(DeviceType.QRCODE_SCANNER);
byte[] buffer = new byte[1000];
usbDev.read(buffer, 100);
for (int i=0; i<7; i++) {
if (isRun() && openBarcode) {
int size = usbDev.read(buffer, 5000);
if (size > 5) {
String barCodeString = new String(buffer, 0, size);
getHandlerMain().start(getLogs().getClassName(), "收到條碼:"+ Tools.randomReplace(barCodeString, 0.3)+",送出交易");
try {
// TODO: [新後台改接] 待未來新後台啟用後此處取貨碼驗證需改串接新的 StarCloudAPI B660 介面
MyApp.getInstance().getMemberVerificationStructure().setProductId("0");
MyApp.getInstance().getMemberVerificationStructure().setProductPrice(0);
MyApp.getInstance().getMemberVerificationStructure().setMemberBarCode(barCodeString);
MyApp.getInstance().getMemberVerificationStructure().setVerificationType(2);
httpAPI.memberVerification(MyApp.getInstance().getMemberVerificationStructure());
} catch (LogsParseException | LogsSettingEmptyException e) {
getLogs().warning(e);
}
openBarcode = false;
}
}
}
usbDev.closePort();
} catch (LogsUnsupportedOperationException | LogsIOException | LogsEmptyException e) {
getLogs().warning(e);
}
}
@Override
protected Class<?> setCls() {
return getClass();
}
}
public int getTotalPrice() {
int totalPrice = 0;
if (MyApp.getInstance().getBuyList().size() == 0) return totalPrice;
for(BuyStructure buy: MyApp.getInstance().getBuyList()) {
totalPrice += buy.getFullPrice();
}
return totalPrice;
}
public void changeDispatch() {
if (isChangeDispatch) {
closeDialogCountdown.shutdown();
isChangeDispatch = false;
changeDialog(DispatchDialog.class);
}
}
public void changeOk() {
try {
String number = binding.editTextNumber.getText().toString();
MyApp.getInstance().getMemberVerificationStructure().setProductId("0");
MyApp.getInstance().getMemberVerificationStructure().setProductPrice(0);
MyApp.getInstance().getMemberVerificationStructure().setMemberBarCode(number);
MyApp.getInstance().getMemberVerificationStructure().setVerificationType(2);
httpAPI.memberVerification(MyApp.getInstance().getMemberVerificationStructure());
} catch (LogsParseException e) {
throw new RuntimeException(e);
} catch (LogsSettingEmptyException e) {
throw new RuntimeException(e);
}
}
public void changeBuy() {
closeDialogCountdown.shutdown();
BuyPickupCodeDialog buyDialog = new BuyPickupCodeDialog(getContext(), getSrcHandlerMain());
buyDialog.show();
cancel();
}
public void dialogCancel() {
closeDialogCountdown.shutdown();
MyApp.getInstance().getBuyList().clear();
cancel();
}
public void cancelOnThreadCountdown() {
MyApp.getInstance().getBuyList().clear();
cancel();
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("返回("+countdown+")");
}
public void setStatusText(String message) {
binding.textStatus.setText(message);
}
public void reportAccessCodeData() {
try {
MyApp.getInstance().getReportAccessCodeStructure().setField(MyApp.getInstance().getReportAccessCodeStructure().getField());
MyApp.getInstance().getReportAccessCodeStructure().setSlot(MyApp.getInstance().getBuyList().get(0).getSlot());
MyApp.getInstance().getReportAccessCodeStructure().setProductId(MyApp.getInstance().getReportAccessCodeStructure().getProductId());
httpAPI.reportAccessCode(MyApp.getInstance().getReportAccessCodeStructure());
} catch (LogsParseException e) {
throw new RuntimeException(e);
} catch (LogsSettingEmptyException e) {
throw new RuntimeException(e);
}
}
public void updatePickupCodeData() {
try {
String number = binding.editTextNumber.getText().toString();
MyApp.getInstance().getMemberVerificationStructure().setProductId("0");
MyApp.getInstance().getMemberVerificationStructure().setProductPrice(0);
MyApp.getInstance().getMemberVerificationStructure().setMemberBarCode(number);
MyApp.getInstance().getMemberVerificationStructure().setVerificationType(2);
httpAPI.memberVerification(MyApp.getInstance().getMemberVerificationStructure());
} catch (LogsParseException e) {
throw new RuntimeException(e);
} catch (LogsSettingEmptyException e) {
throw new RuntimeException(e);
}
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {
public CloseDialogOnThreadHandler(HandlerMain handlerMain) {
super(handlerMain);
handlerMain.start(getClass().getSimpleName(), CheckoutCashDialog.Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(getSrcCountdown()));
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected void close(HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.COUNTDOWN_CANCEL.getOption(), "close dialog");
}
@Override
protected void execute(long countdown, HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(countdown));
}
@Override
protected Class<?> setCls() {
return getClass();
}
}
}

View File

@ -1,193 +0,0 @@
package com.unibuy.smartdevice.ui.recycler;
import android.content.Context;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.unibuy.smartdevice.databinding.RecyclerDialogBuyListBinding;
import com.unibuy.smartdevice.databinding.RecyclerDialogPickupCodeBuyListBinding;
import com.unibuy.smartdevice.devices.SlotField;
import com.unibuy.smartdevice.exception.Logs;
import com.unibuy.smartdevice.exception.LogsEmptyException;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.ui.dialog.BuyDialog;
import com.unibuy.smartdevice.ui.tools.ImageGlide;
import java.util.List;
public class RecyclerDialogPickupCodeBuyListAdpter extends RecyclerView.Adapter<RecyclerDialogPickupCodeBuyListAdpter.ViewHolder> {
private Logs logs;
private Context context;
private final List<BuyStructure> buyList;
private HandlerMain handlerMain;
public RecyclerDialogPickupCodeBuyListAdpter(Context context, List<BuyStructure> buyList, HandlerMain handlerMain) {
this.logs = new Logs(this.getClass());
this.context = context;
this.buyList = buyList;
this.handlerMain = handlerMain;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
RecyclerDialogPickupCodeBuyListBinding binding;
public ViewHolder(@NonNull RecyclerDialogPickupCodeBuyListBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 使用 ViewBinding 來綁定佈局
RecyclerDialogPickupCodeBuyListBinding binding = RecyclerDialogPickupCodeBuyListBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
logs.info("position:" + position);
BuyStructure buyProduct = buyList.get(position);
holder.binding.textPosition.setText(String.valueOf(position + 1));
String productID = buyProduct.getProduct().getProductID();
if (!productID.isEmpty() && !productID.equals("?")) {
holder.binding.textProductName.setText(buyProduct.getProduct().getProductName());
} else {
holder.binding.textProductName.setText("點我選擇商品");
}
// holder.binding.textDelete.setOnClickListener(new textDeleteOnClickListener(position));
String urlString = buyProduct.getProduct().getProductImg();
try {
new ImageGlide(context).fileload(urlString, holder.binding.imageProductPicture);
} catch (LogsEmptyException e) {
this.logs.warning(e);
}
int machinePrice = buyProduct.getProduct().getMachinePrice();
int sellingPrice = buyProduct.getProduct().getSellingPrice();
int memberPrice = buyProduct.getProduct().getMemberPrice();
int slotUpperLimit = buyProduct.getProduct().getSlotUpperLimit();
int count = buyProduct.getCount();
int price = machinePrice > 0? machinePrice: sellingPrice;
holder.binding.textPrice.setText("$"+price);
holder.binding.textCount.setText("x"+String.valueOf(count));
logs.info("field:" + buyProduct.getField());
}
public class addition1OnClickListener implements View.OnClickListener {
private Context context;
private RecyclerDialogBuyListBinding binding;
private int position;
public addition1OnClickListener(Context context, RecyclerDialogBuyListBinding binding, int position) {
this.context = context;
this.binding = binding;
this.position = position;
}
@Override
public void onClick(View v) {
BuyStructure buyProduct = buyList.get(position);
int count = buyProduct.getCount();
count++;
buyList.get(position).setCount(count);
binding.textCount.setText("x"+String.valueOf(count));
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.SET_PRICE.getOption(), "total price");
}
}
public class subtraction1OnClickListener implements View.OnClickListener {
private Context context;
private RecyclerDialogBuyListBinding binding;
private int position;
public subtraction1OnClickListener(Context context, RecyclerDialogBuyListBinding binding, int position) {
this.context = context;
this.binding = binding;
this.position = position;
}
@Override
public void onClick(View v) {
BuyStructure buyProduct = buyList.get(position);
int count = buyProduct.getCount();
count--;
if (count <= 0) {
buyList.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, buyList.size() - position);
handlerMain.start(getClass().getSimpleName(),"刪除一筆資料");
} else {
buyList.get(position).setCount(count);
binding.textCount.setText("x"+String.valueOf(count));
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.SET_PRICE.getOption(),"total price");
}
}
}
public class editCountOnEditorActionListener implements TextView.OnEditorActionListener{
private ViewHolder holder;
private int position;
public editCountOnEditorActionListener(ViewHolder holder, int position) {
this.holder = holder;
this.position = position;
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEND) {
int count = Integer.valueOf(v.getText().toString());
buyList.get(position).setCount(count);
notifyItemChanged(position);
return true;
}
return false;
}
}
public class textDeleteOnClickListener implements View.OnClickListener{
private int position;
public textDeleteOnClickListener(int position) {
this.position = position;
}
@Override
public void onClick(View v) {
delProductData(position);
}
}
@Override
public int getItemCount() {
return buyList.size();
}
public void addProductData(BuyStructure buyProduct) {
buyList.add(buyProduct);
notifyItemInserted(buyList.size() -1);
}
public void delProductData(int position) {
buyList.remove(position);
notifyItemRemoved(position);
}
}

View File

@ -1,271 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/light_gray"
>
<LinearLayout
android:id="@+id/linearLayout5"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/red"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingLeft="20dp"
android:paddingRight="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textVersion"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:text="20250108ver1"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textThreadCount"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:text="TC:0"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnswitchlanTW"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:text="中"
android:visibility="gone"
android:textSize="30sp"
android:textColor="@color/white"
android:textStyle="bold"
/>
<TextView
android:id="@+id/btnswitchlanEn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:text="En"
android:visibility="gone"
android:textSize="30sp"
android:textColor="@color/white"
android:textStyle="bold"
/>
<TextView
android:id="@+id/btnswitchlanJa"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:text="日"
android:visibility="gone"
android:textSize="30sp"
android:textColor="@color/white"
android:textStyle="bold"
/>
<TextView
android:id="@+id/textTemperature"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:text="0 ℃"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textTime"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:text="1911/01/01 01:01:01"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textNetwork"
android:layout_width="150dp"
android:layout_height="match_parent"
android:gravity="center_vertical|end"
android:text="@string/no_internet"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
<RelativeLayout
android:id="@+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="60dp"
android:layout_marginBottom="2dp"
android:background="@color/black"
app:layout_constraintDimensionRatio="18:21"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.media3.ui.PlayerView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:show_subtitle_button="true"
app:use_artwork="true"
app:use_controller="false"
app:resize_mode="fit"
app:surface_type="texture_view" />
<ImageView
android:id="@+id/imageAd"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:visibility="gone"
/>
</RelativeLayout>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/relativeLayout" />
<LinearLayout
android:id="@+id/bottom_navigation"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone"
android:paddingStart="10dp"
android:paddingEnd="10dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonVmc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/drinksale"
app:cornerRadius="12dp"
android:backgroundTint="@android:color/transparent"
app:strokeColor="@color/gray"
android:textSize="38sp"
app:strokeWidth="2dp"
android:visibility="gone"
android:layout_margin="4dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonElectric"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/board_games"
app:cornerRadius="12dp"
android:backgroundTint="@android:color/transparent"
app:strokeColor="@color/gray"
android:textSize="38sp"
android:visibility="gone"
app:strokeWidth="2dp"
android:layout_margin="4dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonOther"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="gone"
android:text="@string/other"
app:cornerRadius="12dp"
android:backgroundTint="@android:color/transparent"
app:strokeColor="@color/gray"
android:textSize="38sp"
app:strokeWidth="2dp"
android:layout_margin="4dp"/>
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/floatButtonPickupCode"
android:layout_width="50dp"
android:layout_height="150dp"
android:text="取\n貨\n碼"
android:textSize="28sp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="80dp"
android:backgroundTint="@color/gray"
android:textColor="@color/black"
android:alpha="0.8"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/floatButtonShoppingCart"
app:layout_constraintEnd_toEndOf="@+id/bottom_navigation"
android:textStyle="bold"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatButtonShoppingCart"
android:layout_width="58dp"
android:layout_height="51dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="24dp"
android:layout_marginBottom="80dp"
android:contentDescription="TODO"
android:visibility="gone"
app:backgroundTint="@color/gray"
app:layout_constraintBottom_toBottomOf="@+id/fragment_container"
app:layout_constraintEnd_toEndOf="@+id/bottom_navigation"
app:maxImageSize="60dp"
app:srcCompat="@drawable/baseline_add_shopping_cart_24" />
<Button
android:id="@+id/btnTopRight"
android:layout_width="200dp"
android:layout_height="100dp"
android:text="進入後台"
android:textSize="24sp"
android:backgroundTint="@color/red"
android:textColor="@android:color/white"
android:alpha="1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
android:layout_marginTop="60dp"
android:layout_marginEnd="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,104 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/light_gray">
<LinearLayout
android:id="@+id/linearLayout4"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@color/light_gray"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@color/red"
android:gravity="center"
android:text="購物車"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginBottom="1dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.15"
android:gravity="center|end"
android:text="合計金額:"
android:textColor="@color/red"
android:textSize="30sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textPrice"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_weight="0.85"
android:gravity="center|start"
android:text="0"
android:textColor="@color/red"
android:textSize="30sp"
android:textStyle="bold" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerDialogBuyList"
android:layout_width="match_parent"
android:layout_height="750dp"
/>
<LinearLayout
android:id="@+id/layoutMemberTransactions"
android:layout_width="match_parent"
android:layout_height="340dp"
android:layout_gravity="center"
android:visibility="gone"
android:orientation="vertical">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/buttonCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/white"
android:text="取消(40)"
android:textSize="20sp"
android:textColor="@color/black" />
<Button
android:id="@+id/buttonGetProduct"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/white"
android:text="取貨"
android:textColor="@color/black"
android:textSize="20sp"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,194 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/light_gray">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/light_gray"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@color/red"
android:gravity="center"
android:text="取貨碼"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginBottom="1dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/textStatus"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:gravity="center"
android:text="輸入取貨碼 或 掃描QRCode"
android:textColor="@color/red"
android:textSize="30sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearMark1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="10dp"
android:paddingBottom="20dp"
android:background="@color/white"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal">
<EditText
android:id="@+id/editTextNumber"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:ems="10"
android:textSize="20sp"
android:hint="取貨碼"
android:gravity="center"
android:inputType="number" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/button10"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="0" />
<Button
android:id="@+id/button11"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="1" />
<Button
android:id="@+id/button12"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="2" />
<Button
android:id="@+id/button13"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="3" />
<Button
android:id="@+id/button14"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="4" />
<Button
android:id="@+id/buttonR1"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/light_gray"
android:text="←" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/button15"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="5" />
<Button
android:id="@+id/button16"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="6" />
<Button
android:id="@+id/button17"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="7" />
<Button
android:id="@+id/button18"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="8" />
<Button
android:id="@+id/button19"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:text="9" />
<Button
android:id="@+id/buttonC1"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/light_gray"
android:text="C" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/buttonCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/white"
android:text="返回(40)"
android:textSize="20sp"
android:textColor="@color/black" />
<Button
android:id="@+id/buttonFinish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/white"
android:text="輸入完成"
android:textSize="20sp"
android:textColor="@color/black" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
tools:context=".ui.devs.vmc.VmcFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerBuy"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="500dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/scanbarcodebtn"
android:layout_width="400dp"
android:layout_height="400dp"
android:layout_marginTop="-50dp"
android:padding="10dp"
android:text="掃描單據\n按鈕"
android:textColor="@android:color/white"
android:textSize="70sp"
app:strokeColor="@color/gray"
app:strokeWidth="5dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,109 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="1dp"
android:paddingBottom="1dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="1dp"
android:background="@color/white"
android:orientation="horizontal"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textPosition"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_marginEnd="2dp"
android:layout_weight="0.3"
android:gravity="center"
android:text="0"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="visible" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<ImageView
android:id="@+id/imageProductPicture"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/default_product"
app:layout_constraintDimensionRatio="16:9" />
</RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical"
tools:layout_editor_absoluteY="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/white"
android:gravity="center_vertical"
android:paddingStart="10dp"
android:orientation="horizontal">
<TextView
android:id="@+id/textProductName"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="商品名稱"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/white"
android:orientation="horizontal">
<TextView
android:id="@+id/textPrice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="$0"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textCount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="x0"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,264 +0,0 @@
package com.unibuy.smartdevice.ui.dialog;
import android.content.Context;
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.databinding.DialogPickupCodeBuyBinding;
import com.unibuy.smartdevice.devices.SlotField;
import com.unibuy.smartdevice.exception.LogsEmptyException;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.HandlerMainCountdown;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
import com.unibuy.smartdevice.tools.Tools;
import com.unibuy.smartdevice.ui.recycler.RecyclerDialogPickupCodeBuyListAdpter;
import java.util.HashMap;
import java.util.Map;
public class BuyPickupCodeDialog extends DialogAbstract {
public enum Option {
TOAST(0),
COUNTDOWN_CANCEL(1),
SET_COUNTDOWN_TEXT(2),
SET_PRICE(3),
RESET_COUNTDOWM(4),
RESET_COUNTDOWM_TOAST(5),
;
private int option;
public int getOption() {
return option;
}
Option(int option) {
this.option = option;
}
}
public static final Map<Integer, Option> optionMap = new HashMap<>();
static {
for (Option option : Option.values()) {
optionMap.put(option.getOption(), option);
}
}
@Override
protected Context setCtx() {
return getContext();
}
@Override
protected Class<? extends DialogAbstract> setCls() {
return getClass();
}
@Override
protected HandlerMain setHandlerMain() {
return new ToastHandlerMain(getContext(), getLogs()) {
@Override
protected void execute(Context context, int commandCode, String message) {
if (MyApp.getInstance().getBuyList().size() <= 0) {
dialogCancel();
} else {
Option option = optionMap.get(commandCode);
switch (option) {
case TOAST:
super.execute(context, commandCode, message);
break;
case COUNTDOWN_CANCEL:
cancelOnThreadCountdown();
break;
case SET_COUNTDOWN_TEXT:
setButtonCancelText(Integer.valueOf(message));
break;
case SET_PRICE:
binding.textPrice.setText(String.valueOf(getTotalPrice()));
closeDialogCountdown.setCountdown(40);
break;
case RESET_COUNTDOWM:
closeDialogCountdown.setCountdown(40);
break;
case RESET_COUNTDOWM_TOAST:
super.execute(context, commandCode, message);
closeDialogCountdown.setCountdown(40);
break;
}
}
}
};
}
private DialogPickupCodeBuyBinding binding;
private RecyclerDialogPickupCodeBuyListAdpter recyclerDialogPickupCodeBuyListAdpter;
private CloseDialogOnThreadHandler closeDialogCountdown;
private boolean isChangeDispatch;
public BuyPickupCodeDialog(Context context, HandlerMain srcHandlerMain) {
super(context, srcHandlerMain);
getLogs().info("open:" + getClass().getSimpleName());
}
@Override
protected void onCreate(Context context) {
binding = DialogPickupCodeBuyBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
isChangeDispatch = true;
this.recyclerDialogPickupCodeBuyListAdpter = new RecyclerDialogPickupCodeBuyListAdpter(getCtx(), MyApp.getInstance().getBuyList(), getHandlerMain());
this.binding.recyclerDialogBuyList.setAdapter(recyclerDialogPickupCodeBuyListAdpter);
this.binding.recyclerDialogBuyList.setLayoutManager(new LinearLayoutManager(context));
MyApp.getInstance().getReportFlowInfoData().setOrderId(Tools.generateTimeBasedUUID());
MyApp.getInstance().getReportFlowInfoData().setFlowType(6);
MyApp.getInstance().getPostInvoiceByGreenData().setOrderId(MyApp.getInstance().getReportFlowInfoData().getOrderId());
this.binding.layoutMemberTransactions.setVisibility(View.GONE);
this.binding.buttonGetProduct.setOnClickListener(v -> gotoGetProduct());
this.binding.buttonCancel.setOnClickListener(v -> dialogCancel());
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
closeDialogCountdown.start(40);
binding.textPrice.setText(String.valueOf(getTotalPrice()));
}
public int getTotalPrice() {
int totalPrice = 0;
if (MyApp.getInstance().getBuyList().size() == 0) return totalPrice;
for(BuyStructure buy: MyApp.getInstance().getBuyList()) {
totalPrice += buy.getFullPrice();
}
return totalPrice;
}
public void gotoGetProduct() {
if (isChangeDispatch) {
closeDialogCountdown.shutdown();
isChangeDispatch = false;
changeDialog(DispatchDialog.class);
cancel();
}
}
public void dialogCancel() {
closeDialogCountdown.shutdown();
int buyListSize = MyApp.getInstance().getBuyList().size();
MyApp.getInstance().getBuyList().clear();
if (buyListSize > 0) {
recyclerDialogPickupCodeBuyListAdpter.notifyItemRangeRemoved(0, buyListSize);
}
cancel();
}
public void cancelOnThreadCountdown() {
int buyListSize = MyApp.getInstance().getBuyList().size();
MyApp.getInstance().getBuyList().clear();
if (buyListSize > 0) {
recyclerDialogPickupCodeBuyListAdpter.notifyItemRangeRemoved(0, buyListSize);
}
getTools().dialogClose();
cancel();
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText(getContext().getString(R.string.cancel)+"("+countdown+")");
}
public void gotoDialog() {
closeDialogCountdown.shutdown();
int flowType = MyApp.getInstance().getReportFlowInfoData().getFlowType();
if (flowType == 5) {
changeCheckout();
} else {
if (MyApp.getInstance().getDevSet().isShoppingCar()) {
if (MyApp.getInstance().getDevSet().isInvoice()) {
changeDialog(InvoiceBarcodeDialog.class);
} else {
changeCheckout();
}
} else {
if (MyApp.getInstance().getDevSet().isInvoice()) {
BuyStructure buyData = MyApp.getInstance().getBuyList().get(0);
try {
SlotField slotField = SlotField.getSlotField(buyData.getField());
if (slotField.isInvoice()) {
changeDialog(InvoiceBarcodeDialog.class);
} else {
changeCheckout();
}
} catch (LogsEmptyException e) {
changeDialog(InvoiceBarcodeDialog.class);
}
} else {
changeCheckout();
}
}
}
}
public void changeCheckout() {
closeDialogCountdown.shutdown();
int flowType = MyApp.getInstance().getReportFlowInfoData().getFlowType();
switch (flowType) {
case 1:
changeDialog(CheckoutNfcCardDialog.class);
break;
case 2:
changeDialog(CheckoutNfcRechargeDialog.class);
break;
case 3:
changeDialog(CheckoutEsunPayDialog.class);
break;
case 4:
changeDialog(CheckoutNfcPhoneDialog.class);
break;
case 5:
changeDialog(CheckoutAccessCodeDialog.class);
break;
case 9:
changeDialog(CashPayDialog.class);
break;
case 70:
changeDialog(CheckoutLinePayDialog.class);
break;
}
}
public static class CloseDialogOnThreadHandler extends HandlerMainCountdown {
public CloseDialogOnThreadHandler(HandlerMain handlerMain) {
super(handlerMain);
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected void close(HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), Option.COUNTDOWN_CANCEL.getOption(), "close dialog");
}
@Override
protected void execute(long countdown, HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(countdown));
}
@Override
protected Class<?> setCls() {
return getClass();
}
}
}

View File

@ -1,194 +0,0 @@
package com.unibuy.smartdevice.ui.recycler;
import android.content.Context;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.databinding.RecyclerDialogBuyListBinding;
import com.unibuy.smartdevice.databinding.RecyclerDialogPickupCodeBuyListBinding;
import com.unibuy.smartdevice.devices.SlotField;
import com.unibuy.smartdevice.exception.Logs;
import com.unibuy.smartdevice.exception.LogsEmptyException;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.ui.dialog.BuyDialog;
import com.unibuy.smartdevice.ui.tools.ImageGlide;
import java.util.List;
public class RecyclerDialogPickupCodeBuyListAdpter extends RecyclerView.Adapter<RecyclerDialogPickupCodeBuyListAdpter.ViewHolder> {
private Logs logs;
private Context context;
private final List<BuyStructure> buyList;
private HandlerMain handlerMain;
public RecyclerDialogPickupCodeBuyListAdpter(Context context, List<BuyStructure> buyList, HandlerMain handlerMain) {
this.logs = new Logs(this.getClass());
this.context = context;
this.buyList = buyList;
this.handlerMain = handlerMain;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
RecyclerDialogPickupCodeBuyListBinding binding;
public ViewHolder(@NonNull RecyclerDialogPickupCodeBuyListBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 使用 ViewBinding 來綁定佈局
RecyclerDialogPickupCodeBuyListBinding binding = RecyclerDialogPickupCodeBuyListBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
logs.info("position:" + position);
BuyStructure buyProduct = buyList.get(position);
holder.binding.textPosition.setText(String.valueOf(position));
String productID = buyProduct.getProduct().getProductID();
if (!productID.isEmpty() && !productID.equals("?")) {
holder.binding.textProductName.setText(buyProduct.getProduct().getProductName());
} else {
holder.binding.textProductName.setText(context.getString(R.string.click_select_product));//
}
// holder.binding.textDelete.setOnClickListener(new textDeleteOnClickListener(position));
String urlString = buyProduct.getProduct().getProductImg();
try {
new ImageGlide(context).fileload(urlString, holder.binding.imageProductPicture);
} catch (LogsEmptyException e) {
this.logs.warning(e);
}
int machinePrice = buyProduct.getProduct().getMachinePrice();
int sellingPrice = buyProduct.getProduct().getSellingPrice();
int memberPrice = buyProduct.getProduct().getMemberPrice();
int slotUpperLimit = buyProduct.getProduct().getSlotUpperLimit();
int count = buyProduct.getCount();
int price = machinePrice > 0? machinePrice: sellingPrice;
holder.binding.textPrice.setText("$"+price);
holder.binding.textCount.setText("x"+String.valueOf(count));
logs.info("field:" + buyProduct.getField());
}
public class addition1OnClickListener implements View.OnClickListener {
private Context context;
private RecyclerDialogBuyListBinding binding;
private int position;
public addition1OnClickListener(Context context, RecyclerDialogBuyListBinding binding, int position) {
this.context = context;
this.binding = binding;
this.position = position;
}
@Override
public void onClick(View v) {
BuyStructure buyProduct = buyList.get(position);
int count = buyProduct.getCount();
count++;
buyList.get(position).setCount(count);
binding.textCount.setText("x"+String.valueOf(count));
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.SET_PRICE.getOption(), "total price");
}
}
public class subtraction1OnClickListener implements View.OnClickListener {
private Context context;
private RecyclerDialogBuyListBinding binding;
private int position;
public subtraction1OnClickListener(Context context, RecyclerDialogBuyListBinding binding, int position) {
this.context = context;
this.binding = binding;
this.position = position;
}
@Override
public void onClick(View v) {
BuyStructure buyProduct = buyList.get(position);
int count = buyProduct.getCount();
count--;
if (count <= 0) {
buyList.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, buyList.size() - position);
handlerMain.start(getClass().getSimpleName(),context.getString(R.string.delete_a_record));//刪除一筆資料
} else {
buyList.get(position).setCount(count);
binding.textCount.setText("x"+String.valueOf(count));
handlerMain.start(getClass().getSimpleName(), BuyDialog.Option.SET_PRICE.getOption(),"total price");
}
}
}
public class editCountOnEditorActionListener implements TextView.OnEditorActionListener{
private ViewHolder holder;
private int position;
public editCountOnEditorActionListener(ViewHolder holder, int position) {
this.holder = holder;
this.position = position;
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEND) {
int count = Integer.valueOf(v.getText().toString());
buyList.get(position).setCount(count);
notifyItemChanged(position);
return true;
}
return false;
}
}
public class textDeleteOnClickListener implements View.OnClickListener{
private int position;
public textDeleteOnClickListener(int position) {
this.position = position;
}
@Override
public void onClick(View v) {
delProductData(position);
}
}
@Override
public int getItemCount() {
return buyList.size();
}
public void addProductData(BuyStructure buyProduct) {
buyList.add(buyProduct);
notifyItemInserted(buyList.size() -1);
}
public void delProductData(int position) {
buyList.remove(position);
notifyItemRemoved(position);
}
}

View File

@ -1,168 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/light_gray">
<LinearLayout
android:id="@+id/linearLayout4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/light_gray"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/red"
android:gravity="center"
android:text="現金支付"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="280dp"
android:layout_marginBottom="1dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.1"
android:gravity="center|end"
android:text="訂單金額:"
android:textColor="@color/red"
android:textSize="40sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textPrice"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_weight="0.9"
android:gravity="center|start"
android:text="0"
android:textColor="@color/red"
android:textSize="40sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="680dp"
android:layout_marginBottom="1dp"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/testOne"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_weight="0.90"
android:gravity="center"
android:text="請投入硬幣或紙鈔"
android:textColor="@color/red"
android:textSize="80sp"
android:textStyle="bold" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/cash"
/>
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_weight="0.90"
android:gravity="center"
android:text="已投入的金額:"
android:textColor="#0060E2"
android:textSize="40sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textStatus2"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_weight="0.90"
android:gravity="center"
android:text="未投入的金額:"
android:textColor="#ff0000"
android:textSize="40sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:background="@null"
android:scaleType="fitCenter"
android:visibility="gone"
app:srcCompat="@drawable/checkout_wait" />
</LinearLayout>
<LinearLayout
android:layout_marginTop="40dp"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/buttonCancel"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:backgroundTint="@color/white"
android:text="取消並退幣"
android:textSize="28sp"
android:textColor="@color/black" />
<Button
android:id="@+id/buttonChange"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/white"
android:text="@string/replacement_payment"
android:visibility="gone"
android:textSize="28sp"
android:textColor="@color/black" />
<Button
android:id="@+id/buttonRetryEscrow"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:backgroundTint="#FF9800"
android:text="嘗試退鈔並重試"
android:visibility="gone"
android:textSize="28sp"
android:textColor="@color/white" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General flavor multi-item shopping cart -->
<string name="shopping_cart_title">Shopping Cart</string>
<string name="add_more_items">Add More Items</string>
<string name="go_to_pay">Proceed to Checkout</string>
<string name="cancel">Cancel</string>
<string name="cancel_button_format">Cancel (%d sec)</string>
<string name="total_items_format">Items: (%d)</string>
<string name="total_amount_format">NT$ %d</string>
<string name="cart_out_of_stock">Out of Stock</string>
<string name="cart_cannot_decrease">Cannot Decrease Further</string>
<string name="cart_max_items">Up to 8 items per cart</string>
<string name="cart_added">Added to cart</string>
<string name="data_error">Data Error</string>
</resources>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General flavor 多品目ショッピングカート -->
<string name="shopping_cart_title">ショッピングカート</string>
<string name="add_more_items">商品を追加</string>
<string name="go_to_pay">お会計へ進む</string>
<string name="cancel">キャンセル</string>
<string name="cancel_button_format">キャンセル(%d秒</string>
<string name="total_items_format">商品数:(%d)</string>
<string name="total_amount_format">NT$ %d</string>
<string name="cart_out_of_stock">在庫切れ</string>
<string name="cart_cannot_decrease">これ以上減らせません</string>
<string name="cart_max_items">カートは最大8点まで購入できます</string>
<string name="cart_added">カートに追加しました</string>
<string name="data_error">データ異常</string>
</resources>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
General flavor 的 DevSet 預設覆寫:裝上即可用多品項購物車 + 指定付款方式。
(現金、刷卡/NFC、玉山掃碼、LINE Pay 掃碼、發票、購物車)
其餘沿用 main 預設TapPay 維持開啟,可於主控台關閉)。
-->
<resources>
<bool name="devset_default_shopping_cart">true</bool>
<bool name="devset_default_cash">true</bool>
<bool name="devset_default_line_pay">true</bool>
<!-- General 顯示前台取貨碼浮鈕 -->
<bool name="show_pickup_code_button">true</bool>
</resources>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General flavor 多品項購物車 -->
<string name="shopping_cart_title">購物車</string>
<string name="add_more_items">加購商品</string>
<string name="go_to_pay">前往結帳</string>
<string name="cancel">取消</string>
<string name="cancel_button_format">取消(%d秒</string>
<string name="total_items_format">商品數量:(%d)</string>
<string name="total_amount_format">NT$ %d</string>
<string name="cart_out_of_stock">超出庫存</string>
<string name="cart_cannot_decrease">不能減少了</string>
<string name="cart_max_items">購物車最多可購買 8 件商品</string>
<string name="cart_added">已加入購物車</string>
<string name="data_error">數據異常</string>
</resources>

View File

@ -6,8 +6,13 @@ public final class AppEntry {
private AppEntry() {
}
/**
* 是否為取貨單購買流程 Cmuh 中國醫版
* 改由雲端 B014 shopping_mode 決定pickup_sheet取代舊 project flavor 判定
* 開機時 {@code MyApp.initialSystemSet} 已載入持久化值 {@link #initialActivity()} 取得到
*/
public static boolean isCmuh() {
return "Cmuh".equalsIgnoreCase(BuildConfig.FLAVOR_project);
return "pickup_sheet".equals(MyApp.getInstance().getShoppingMode());
}
public static Class<?> initialActivity() {

View File

@ -110,6 +110,9 @@ public class MyApp extends Application {
private EsunpayStructure esunpay;
private MachineStructure machine;
private DevSetStructure devSet;
// 購買流程模式basic多品項購物車/ employee_card員工卡/ pickup_sheet取貨單
// 由雲端 B014 ShoppingMode 下發取代舊 project flavor 分家預設 basic
private String shoppingMode = "basic";
private GreenInvoiceStructure greenInvoice;
private GreenpaySettingStructure greenpaySetting;
private GreenpayMerchantStructure greenpayMerchant;
@ -209,7 +212,9 @@ public class MyApp extends Application {
devSetRes.getBoolean(R.bool.devset_default_tap_pay_31),
devSetRes.getBoolean(R.bool.devset_default_tap_pay_32),
devSetRes.getBoolean(R.bool.devset_default_tap_pay_33),
devSetRes.getBoolean(R.bool.devset_default_tap_pay_34));
devSetRes.getBoolean(R.bool.devset_default_tap_pay_34),
devSetRes.getBoolean(R.bool.devset_default_pickup_code),
devSetRes.getBoolean(R.bool.devset_default_pass_code));
this.machine = new MachineStructure("");
this.esunpay = new EsunpayStructure("", "", "");
this.greenInvoice = new GreenInvoiceStructure("", "", "", "");
@ -348,6 +353,15 @@ public class MyApp extends Application {
MyApp.getInstance().getDevSet().setDevTapPay(jsonObject.getBoolean("DevTapPay"));
MyApp.getInstance().getDevSet().setDevCash(jsonObject.getBoolean("DevCash"));
MyApp.getInstance().getDevSet().setDevLinePay(jsonObject.getBoolean("DevLinePay"));
// TapPay30~34 與取貨碼/通行碼 optBoolean 容忍舊紀錄缺 key避免整段載入中斷
MyApp.getInstance().getDevSet().setTapPay30(jsonObject.optBoolean("TapPay30", false));
MyApp.getInstance().getDevSet().setTapPay31(jsonObject.optBoolean("TapPay31", false));
MyApp.getInstance().getDevSet().setTapPay32(jsonObject.optBoolean("TapPay32", false));
MyApp.getInstance().getDevSet().setTapPay33(jsonObject.optBoolean("TapPay33", false));
MyApp.getInstance().getDevSet().setTapPay34(jsonObject.optBoolean("TapPay34", false));
MyApp.getInstance().getDevSet().setPickupCode(jsonObject.optBoolean("PickupCode", false));
MyApp.getInstance().getDevSet().setPassCode(jsonObject.optBoolean("PassCode", false));
MyApp.getInstance().setShoppingMode(jsonObject.optString("ShoppingMode", "basic"));
} catch (JSONException e) {
new Logs(MyApp.class).warning(e);
}
@ -383,6 +397,14 @@ public class MyApp extends Application {
systemSetByJsonObject.put("DevTapPay", MyApp.getInstance().getDevSet().isDevTapPay());
systemSetByJsonObject.put("DevCash", MyApp.getInstance().getDevSet().isDevCash());
systemSetByJsonObject.put("DevLinePay", MyApp.getInstance().getDevSet().isDevLinePay());
systemSetByJsonObject.put("TapPay30", MyApp.getInstance().getDevSet().isTapPay30());
systemSetByJsonObject.put("TapPay31", MyApp.getInstance().getDevSet().isTapPay31());
systemSetByJsonObject.put("TapPay32", MyApp.getInstance().getDevSet().isTapPay32());
systemSetByJsonObject.put("TapPay33", MyApp.getInstance().getDevSet().isTapPay33());
systemSetByJsonObject.put("TapPay34", MyApp.getInstance().getDevSet().isTapPay34());
systemSetByJsonObject.put("PickupCode", MyApp.getInstance().getDevSet().isPickupCode());
systemSetByJsonObject.put("PassCode", MyApp.getInstance().getDevSet().isPassCode());
systemSetByJsonObject.put("ShoppingMode", MyApp.getInstance().getShoppingMode());
JSONObject cashSetByJsonObject = new JSONObject();
cashSetByJsonObject.put("BillF1000", MyApp.getInstance().getCashSetStructure().isBillF1000());
@ -888,6 +910,14 @@ public class MyApp extends Application {
return devSet;
}
public String getShoppingMode() {
return shoppingMode;
}
public void setShoppingMode(String shoppingMode) {
this.shoppingMode = (shoppingMode == null || shoppingMode.isEmpty()) ? "basic" : shoppingMode;
}
public ReportAccessCodeStructure getReportAccessCodeStructure() {
return reportAccessCodeStructure;

View File

@ -46,14 +46,16 @@ public enum ComPort {
}
public String getFilepath() {
// 下位機接線晟崴(Chengwai) 為最新硬體使用 ttyS7/ttyS6
// 通用版(General) 與中國醫(Cmuh) 下位機相同使用 ttyS1/ttyS2
boolean isChengwai = "Chengwai".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project);
// 下位機接線由硬體 flavorIPC 世代 / 安卓版本決定與購買流程 shopping_mode 正交
// S12XYU新板原晟崴 10")→ ttyS7/ttyS6
// S9XYU Android 9 原中國醫/通用 21.5")→ ttyS1/ttyS2。
// 單一 flavor 維度hardwareAGP 僅產生 BuildConfig.FLAVOR值為 S12XYU / S9XYU
boolean isS12 = "S12XYU".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR);
if (this == COM_PORT_S1) {
return isChengwai ? "/dev/ttyS7" : "/dev/ttyS1";
return isS12 ? "/dev/ttyS7" : "/dev/ttyS1";
}
if (this == COM_PORT_S2) {
return isChengwai ? "/dev/ttyS6" : "/dev/ttyS2";
return isS12 ? "/dev/ttyS6" : "/dev/ttyS2";
}
return filepath;
}

View File

@ -15,6 +15,8 @@ import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.database.ProductsDao;
import com.unibuy.smartdevice.database.SettingsDao;
import com.unibuy.smartdevice.structure.CashSetStructure;
import com.unibuy.smartdevice.structure.DevSetStructure;
import com.unibuy.smartdevice.structure.ProductStructure;
import com.unibuy.smartdevice.structure.ReportSlotListStructure;
import com.unibuy.smartdevice.structure.SlotStructure;
@ -804,7 +806,52 @@ public class StarCloudAPI {
MyApp.getInstance().getGreenInvoice().setGreenHashIV(config.optString("t050v36", ""));
MyApp.getInstance().getGreenInvoice().setGreenEmail(config.optString("t050v38", ""));
// 5. 重要將所有設定同步回資料庫防止 App 重啟後消失
// 5. 解析雲端功能開關缺漏欄位 false缺漏整個物件 不動本地避免連線異常把現場全關
JSONObject devSet = config.optJSONObject("DevSet");
if (devSet != null) {
DevSetStructure d = MyApp.getInstance().getDevSet();
d.setShoppingCar(devSet.optBoolean("ShoppingCar", false));
d.setInvoice(devSet.optBoolean("Invoice", false));
d.setDevNFCPay(devSet.optBoolean("DevNFCPay", false));
d.setDevEsunPay(devSet.optBoolean("DevEsunPay", false));
d.setDevTapPay(devSet.optBoolean("DevTapPay", false));
d.setDevCash(devSet.optBoolean("DevCash", false));
d.setDevLinePay(devSet.optBoolean("DevLinePay", false));
d.setTapPay30(devSet.optBoolean("TapPay30", false));
d.setTapPay31(devSet.optBoolean("TapPay31", false));
d.setTapPay32(devSet.optBoolean("TapPay32", false));
d.setTapPay33(devSet.optBoolean("TapPay33", false));
d.setTapPay34(devSet.optBoolean("TapPay34", false));
// VMC/Electic 雲端不下發 不碰保留本地值
}
JSONObject cashSet = config.optJSONObject("CashSet");
if (cashSet != null) {
CashSetStructure c = MyApp.getInstance().getCashSetStructure();
c.setBillF1000(cashSet.optBoolean("BillF1000", false));
c.setBillE500(cashSet.optBoolean("BillE500", false));
c.setBillD100(cashSet.optBoolean("BillD100", false));
c.setCoinF50(cashSet.optBoolean("CoinF50", false));
c.setCoinE10(cashSet.optBoolean("CoinE10", false));
c.setCoinD5(cashSet.optBoolean("CoinD5", false));
c.setCoinC1(cashSet.optBoolean("CoinC1", false));
}
// 6. FunctionSet 取貨碼/通行碼本次只接這兩項迎賓禮/會員/環溫留待未來
JSONObject funcSet = config.optJSONObject("FunctionSet");
if (funcSet != null) {
DevSetStructure d = MyApp.getInstance().getDevSet();
d.setPickupCode(funcSet.optBoolean("PickupCode", false));
d.setPassCode(funcSet.optBoolean("PassCode", false));
}
// 6b. ShoppingMode購買流程模式basic/employee_card/pickup_sheet取代舊 project flavor
boolean hasShoppingMode = config.has("ShoppingMode");
if (hasShoppingMode) {
MyApp.getInstance().setShoppingMode(config.optString("ShoppingMode", "basic"));
}
if (devSet != null || cashSet != null || funcSet != null || hasShoppingMode) {
MyApp.saveSystemSet(handlerMain.getContext());
}
// 7. 重要將所有設定同步回資料庫防止 App 重啟後消失
persistCurrentSettings();
MyApp.getInstance().initMqtt();
}

View File

@ -15,10 +15,14 @@ public class DevSetStructure {
private boolean isTapPay32;
private boolean isTapPay33;
private boolean isTapPay34;
// 取貨碼/通行碼改由雲端 B014 FunctionSet 下發控制預設值來自 flavor devset_defaults.xml
private boolean isPickupCode;
private boolean isPassCode;
public DevSetStructure(boolean isVMC, boolean isElectic, boolean isShoppingCar, boolean isInvoice,
boolean isDevNFCPay, boolean isDevEsunPay, boolean isDevTapPay, boolean isDevCash, boolean isDevLinePay,
boolean isTapPay30,boolean isTapPay31,boolean isTapPay32,boolean isTapPay33, boolean isTapPay34) {
boolean isTapPay30,boolean isTapPay31,boolean isTapPay32,boolean isTapPay33, boolean isTapPay34,
boolean isPickupCode, boolean isPassCode) {
this.isVMC = isVMC;
this.isElectic = isElectic;
this.isShoppingCar = isShoppingCar;
@ -33,6 +37,8 @@ public class DevSetStructure {
this.isTapPay32 = isTapPay32;
this.isTapPay33 = isTapPay33;
this.isTapPay34 = isTapPay34;
this.isPickupCode = isPickupCode;
this.isPassCode = isPassCode;
}
public boolean isVMC() {
@ -124,4 +130,20 @@ public class DevSetStructure {
public void setTapPay33(boolean tapPay33) {isTapPay33 = tapPay33;}
public void setTapPay34(boolean tapPay34) {isTapPay34 = tapPay34;}
public boolean isPickupCode() {
return isPickupCode;
}
public void setPickupCode(boolean pickupCode) {
isPickupCode = pickupCode;
}
public boolean isPassCode() {
return isPassCode;
}
public void setPassCode(boolean passCode) {
isPassCode = passCode;
}
}

View File

@ -1,6 +1,7 @@
package com.unibuy.smartdevice.ui;
import android.content.Context;
import com.blankj.utilcode.util.ToastUtils;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.structure.SlotStructure;
@ -11,52 +12,42 @@ import com.unibuy.v2.UnibuyHelper;
import com.unibuy.v2.ui.ShoppingCartFragment;
/**
* General (通用版) 銷售流程處理器
* basic 模式 General 通用版銷售流程
* 結帳時彈出標準付款 Dialog (BuyDialog)購物車浮鈕則開啟多品項購物車 Fragment
*/
public class SaleFlowHandler {
protected final Context context;
protected final SaleFlowCallback callback;
protected final HandlerMain handlerMain;
public class BasicSaleFlow extends SaleFlowHandler {
/** 多品項購物車 Fragment懶建立重複開啟時沿用同一實例。 */
private ShoppingCartFragment cartFragment;
public SaleFlowHandler(Context context, HandlerMain handlerMain, SaleFlowCallback callback) {
this.context = context;
this.handlerMain = handlerMain;
this.callback = callback;
}
public void onSetupUI() {
// 通用版 UI 設定預設無動作
}
public void onSetupUI(android.view.View rootView) {
// 通用版 UI 設定預設無動作
public BasicSaleFlow(Context context, HandlerMain handlerMain, SaleFlowCallback callback) {
super(context, handlerMain, callback);
}
@Override
public void onBuyRequested() {
new BuyDialog(context, handlerMain).show();
}
/**
* 點選 VMC 商品加入多品項購物車並停留商品列表不做取貨口檢查出貨時才檢查
* @return true 表示已消化此次點擊General 一律 true
* @return true 表示已消化此次點擊
*/
@Override
public boolean handleProductSelected(SlotStructure slot) {
UnibuyHelper.INSTANCE.getCartViewModel().addToCart(slot);
ToastUtils.showShort(R.string.cart_added);
return true;
}
/** General 使用 Luzui 移植的現金強化版(卡鈔/找零保障/自癒),上報接 TaiwanStar 新 MQTT。 */
/** basic 使用 Luzui 移植的現金強化版(卡鈔/找零保障/自癒),上報接 TaiwanStar 新 MQTT。 */
@Override
public Class<? extends com.unibuy.smartdevice.DialogAbstract> cashDialogClass() {
return com.unibuy.smartdevice.ui.dialog.CashPayDialogGeneral.class;
}
/** 購物車浮鈕:開啟多品項購物車 Fragment。 */
@Override
public void onShoppingCartRequested() {
if (!(context instanceof FontendActivity)) {
return;
@ -67,12 +58,8 @@ public class SaleFlowHandler {
((FontendActivity) context).openFragment(cartFragment);
}
public void onBarcodeReceived(String rawData) {
// 通用版不處理掃碼
}
@Override
public void onPickupRequested() {
// 通用版取貨碼開啟取貨碼輸入 Dialog
new FontendPickupCodeDialog(context, handlerMain).show();
}
}

View File

@ -0,0 +1,23 @@
package com.unibuy.smartdevice.ui;
import android.content.Context;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.ui.dialog.MemberCardPickupDialog;
/**
* employee_card 模式 Chengwai 晟崴版銷售流程
* 點選商品/購物車時直接進入員工卡刷卡取貨 Dialog (MemberCardPickupDialog, B680)
* 商品點擊不消化現金走標準 Dialog無取貨碼入口 皆沿用基底預設
*/
public class EmployeeCardSaleFlow extends SaleFlowHandler {
public EmployeeCardSaleFlow(Context context, HandlerMain handlerMain, SaleFlowCallback callback) {
super(context, handlerMain, callback);
}
@Override
public void onBuyRequested() {
new MemberCardPickupDialog(context, handlerMain).show();
}
}

View File

@ -426,7 +426,7 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
setContentView(binding.getRoot());
// 初始化銷售流程處理器
saleFlowHandler = new SaleFlowHandler(this, getHandlerMain(), this);
saleFlowHandler = SaleFlowHandler.create(this, getHandlerMain(), this);
saleFlowHandler.onSetupUI();
httpAPI = new HttpAPI(getHandlerMain());
@ -713,9 +713,10 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
}
});
// 前台取貨碼浮鈕 flavor 設定決定是否顯示General 顯示其餘維持隱藏
// 前台取貨碼浮鈕改由雲端 B014 FunctionSet.PickupCode 下發控制DevSet.isPickupCode
// 雲端配好前由 flavor devset_defaults 提供預設General=true
binding.floatButtonPickupCode.setVisibility(
getResources().getBoolean(R.bool.show_pickup_code_button) ? VISIBLE : View.GONE);
MyApp.getInstance().getDevSet().isPickupCode() ? VISIBLE : View.GONE);
binding.floatButtonShoppingCart.setOnClickListener(view -> {
// 改由 flavor SaleFlowHandler 決定General 開多品項購物車 Fragment

View File

@ -36,7 +36,7 @@ import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
* Cmuh (中國醫版) 銷售流程處理器
* pickup_sheet 模式 Cmuh 中國醫版銷售流程
* 負責
* 1. 綁定40秒內刷條碼按鈕 (scanbarcodebtn)
* 2. 連接 USB 掃描器背景讀取
@ -44,64 +44,37 @@ import javax.crypto.spec.SecretKeySpec;
* 4. 庫存核對與自動加入購物車
* 5. 顯示領藥與錯誤 Dialog
*/
public class SaleFlowHandler {
public class PickupSheetSaleFlow extends SaleFlowHandler {
private static final String ORDC_EMPTY = "ORDC_EMPTY";
private static final String MATERIAL_NOT_FOUND = "MATERIAL_NOT_FOUND";
private static final String STOCK_EMPTY = "STOCK_EMPTY";
private static final String STOCK_NOT_ENOUGH = "STOCK_NOT_ENOUGH";
protected final Context context;
protected final SaleFlowCallback callback;
protected final HandlerMain handlerMain;
private boolean openBarcode = true;
private boolean haveValuestate = false;
private long lastClickTime = 0;
public SaleFlowHandler(Context context, HandlerMain handlerMain, SaleFlowCallback callback) {
this.context = context;
this.handlerMain = handlerMain;
this.callback = callback;
public PickupSheetSaleFlow(Context context, HandlerMain handlerMain, SaleFlowCallback callback) {
super(context, handlerMain, callback);
}
/**
* Activity onResume() 中會被呼叫此時尋找並綁定 VmcFragment 中的掃碼按鈕
*/
public void onSetupUI() {
onSetupUI(null);
}
@Override
public void onSetupUI(View rootView) {
if (context instanceof Activity) {
View btnScan = rootView != null ? rootView.findViewById(R.id.scanbarcodebtn) : ((Activity) context).findViewById(R.id.scanbarcodebtn);
if (btnScan != null) {
btnScan.setOnClickListener(new BtnQrCodeOnClick(handlerMain, context));
MyApp.getInstance().getLogs().info("✅ 成功在 Cmuh SaleFlowHandler 綁定掃碼按鈕!");
MyApp.getInstance().getLogs().info("✅ 成功在 PickupSheetSaleFlow 綁定掃碼按鈕!");
} else {
MyApp.getInstance().getLogs().info("❌ 在 Cmuh SaleFlowHandler 找不到掃碼按鈕 R.id.scanbarcodebtn");
MyApp.getInstance().getLogs().info("❌ 在 PickupSheetSaleFlow 找不到掃碼按鈕 R.id.scanbarcodebtn");
}
}
}
public void onBuyRequested() {
// 中國醫版沒有購物車付款此方法不執行
}
public void onShoppingCartRequested() {
// 中國醫版維持原行為不處理購物車浮鈕
onBuyRequested();
}
/** 中國醫版不消化商品點擊,交回 adapter 既有流程。 */
public boolean handleProductSelected(com.unibuy.smartdevice.structure.SlotStructure slot) {
return false;
}
/** 中國醫版使用標準現金 Dialog。 */
public Class<? extends com.unibuy.smartdevice.DialogAbstract> cashDialogClass() {
return com.unibuy.smartdevice.ui.dialog.CashPayDialog.class;
}
@Override
public void onBarcodeReceived(String rawData) {
// 若是經由其他方式傳入的 Barcode 可在此處直接處理
try {
@ -114,8 +87,9 @@ public class SaleFlowHandler {
}
/**
* CMUH 點選取貨按鈕開啟輸入取貨碼的 Dialog
* 點選取貨按鈕開啟輸入取貨碼的 Dialogpickup_sheet 走舊 API 驗證 FontendPickupCodeDialog
*/
@Override
public void onPickupRequested() {
new FontendPickupCodeDialog(context, handlerMain).show();
}
@ -144,7 +118,7 @@ public class SaleFlowHandler {
UsbDev usbDev = new UsbDev(DeviceType.QRCODE_SCANNER);
byte[] buffer = new byte[1000];
usbDev.read(buffer, 100); // 預讀清除緩衝
for (int i = 0; i < 40; i++) { // 總計約 40
if (isRun() && openBarcode) {
int size = usbDev.read(buffer, 1000); // 超時為 1
@ -231,7 +205,7 @@ public class SaleFlowHandler {
if (encryptedBytes.length % 16 != 0) {
throw new IllegalArgumentException("錯誤:加密數據長度不是 16 的倍數,可能需要填充");
}
byte[] keyBytes = keyString.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
@ -246,7 +220,7 @@ public class SaleFlowHandler {
String decryptedStr = MyApp.getInstance().getDecryptedData();
MyApp.getInstance().getLogs().info("decryptedStr=" + decryptedStr);
JSONObject jsonObject = new JSONObject(decryptedStr);
if (checkORDCDispatchNumber(decryptedStr)) {
return false;
@ -305,7 +279,7 @@ public class SaleFlowHandler {
JSONObject itemObject = itemsArray.getJSONObject(i);
String ordc = itemObject.optString("ORDC", "").trim();
int requestQty = safeParseInt(itemObject.optString("QTY"), 0);
// 重新初始化這些變數
MedicineName = "";
MedicineCount = 0;
@ -332,10 +306,10 @@ public class SaleFlowHandler {
for (int i = 0; i < itemsArray.length(); i++) {
JSONObject itemObject = itemsArray.getJSONObject(i);
int requestQty = safeParseInt(itemObject.optString("QTY"), 0);
if (requestQty <= medicineList.get(i).getMedicineCount()) {
haveValuestate = true;
if (requestQty > 1) {
class TempSlot {
int slotNo;

View File

@ -0,0 +1,79 @@
package com.unibuy.smartdevice.ui;
import android.content.Context;
import android.view.View;
import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.structure.SlotStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
/**
* 銷售流程處理器抽象基底
*
* 三種購買流程改由雲端 B014 {@code shopping_mode} 執行期切換取代舊 project flavor 分家
* <ul>
* <li>{@code basic} {@link BasicSaleFlow}多品項購物車 General</li>
* <li>{@code employee_card} {@link EmployeeCardSaleFlow}員工卡 B680 Chengwai</li>
* <li>{@code pickup_sheet} {@link PickupSheetSaleFlow}掃碼醫令自動結帳 Cmuh</li>
* </ul>
* {@link #create} {@link MyApp#getShoppingMode()} 建立對應策略
*/
public abstract class SaleFlowHandler {
protected final Context context;
protected final SaleFlowCallback callback;
protected final HandlerMain handlerMain;
protected SaleFlowHandler(Context context, HandlerMain handlerMain, SaleFlowCallback callback) {
this.context = context;
this.handlerMain = handlerMain;
this.callback = callback;
}
/** 依雲端 shopping_mode 建立對應的銷售流程策略(預設 basic。 */
public static SaleFlowHandler create(Context context, HandlerMain handlerMain, SaleFlowCallback callback) {
String mode = MyApp.getInstance().getShoppingMode();
if ("employee_card".equals(mode)) {
return new EmployeeCardSaleFlow(context, handlerMain, callback);
}
if ("pickup_sheet".equals(mode)) {
return new PickupSheetSaleFlow(context, handlerMain, callback);
}
return new BasicSaleFlow(context, handlerMain, callback);
}
public void onSetupUI() {
onSetupUI(null);
}
public void onSetupUI(View rootView) {
// 預設無動作
}
public void onBuyRequested() {
// 預設無動作
}
public void onShoppingCartRequested() {
// 預設購物車浮鈕等同進入購買流程
onBuyRequested();
}
public boolean handleProductSelected(SlotStructure slot) {
// 預設不消化商品點擊交回 adapter 既有流程
return false;
}
public Class<? extends DialogAbstract> cashDialogClass() {
return com.unibuy.smartdevice.ui.dialog.CashPayDialog.class;
}
public void onBarcodeReceived(String rawData) {
// 預設無動作
}
public void onPickupRequested() {
// 預設無動作
}
}

View File

@ -50,11 +50,10 @@ public class SystemSetActivity extends AppCompatActivityAbstract {
setContentView(binding.getRoot());
updateCheckBoxes();
lockAllReadOnly();
binding.buttonBack.setOnClickListener(v -> {
updateSystemSet();
finish();
});
// 設定改以雲端 star-cloud B014 為唯一真相主控台僅供檢視唯讀返回不回寫覆蓋雲端值
binding.buttonBack.setOnClickListener(v -> finish());
// IC 卡讀取功能已移除
}
@ -85,6 +84,32 @@ public class SystemSetActivity extends AppCompatActivityAbstract {
binding.checkboxDevCoinC.setChecked(MyApp.getInstance().getCashSetStructure().isCoinC1());
}
/** 全部設定鎖為唯讀:值由雲端 B014 下發,現場僅能檢視不可修改。 */
private void lockAllReadOnly() {
binding.checkboxVMC.setEnabled(false);
binding.checkboxElectic.setEnabled(false);
binding.checkboxShoppingCar.setEnabled(false);
binding.checkboxInvoice.setEnabled(false);
binding.checkboxDevNFCPay.setEnabled(false);
binding.checkboxDevEsunPay.setEnabled(false);
binding.checkboxDevTapPay.setEnabled(false);
binding.checkboxDevCash.setEnabled(false);
binding.checkboxDevLinePay.setEnabled(false);
binding.checkboxDevTapPay30.setEnabled(false);
binding.checkboxDevTapPay31.setEnabled(false);
binding.checkboxDevTapPay32.setEnabled(false);
binding.checkboxDevTapPay33.setEnabled(false);
binding.checkboxDevTapPay34.setEnabled(false);
binding.checkboxDevBillF.setEnabled(false);
binding.checkboxDevBillE.setEnabled(false);
binding.checkboxDevBillD.setEnabled(false);
binding.checkboxDevCoinF.setEnabled(false);
binding.checkboxDevCoinE.setEnabled(false);
binding.checkboxDevCoinD.setEnabled(false);
binding.checkboxDevCoinC.setEnabled(false);
}
private void updateSystemSet() {
MyApp.getInstance().getDevSet().setVMC(binding.checkboxVMC.isChecked());
MyApp.getInstance().getDevSet().setElectic(binding.checkboxElectic.isChecked());

View File

@ -163,6 +163,11 @@ public class VmcFragment extends FragmentAbstract {
binding.recyclerBuy.setAdapter(recyclerVmcBuyListAdpter);
binding.recyclerBuy.setLayoutManager(new GridLayoutManager(getContext(), 3));
// pickup_sheet Cmuh模式顯示大掃描單據按鈕隱藏商品清單其餘模式相反
boolean isPickupSheet = "pickup_sheet".equals(com.unibuy.smartdevice.MyApp.getInstance().getShoppingMode());
binding.layoutScanBarcode.setVisibility(isPickupSheet ? View.VISIBLE : View.GONE);
binding.recyclerBuy.setVisibility(isPickupSheet ? View.GONE : View.VISIBLE);
// for (int i = 0; i < MyApp.getInstance().getSlotList(SlotField.VMC.getField()).size(); i++) {
// SlotStructure slot = MyApp.getInstance().getSlotList(SlotField.VMC.getField()).get(i);
// if (!slot.getProduct().getProductID().isEmpty() && !slot.getProduct().getProductName().isEmpty() && slot.getProduct().getSellingPrice() > 0) {

View File

@ -158,6 +158,11 @@ public class BuyDialog extends DialogAbstract {
closeDialogCountdown.setCountdown(40);
});
// 通行碼入口改由雲端 B014 FunctionSet.PassCode 下發控制DevSet.isPassCode
// 關閉時隱藏通行碼分頁鈕避免進入 layoutVerificationPickup buttonAccessCode(flowType 5)
this.binding.buttonVerificationPickup.setVisibility(
MyApp.getInstance().getDevSet().isPassCode() ? View.VISIBLE : View.GONE);
this.binding.buttonMemberTransactions.setOnClickListener(view -> {
});

View File

@ -258,7 +258,7 @@ public class DispatchDialog extends DialogAbstract {
httpAPI = new HttpAPI(getHandlerMain());
// 初始化 Finalize Builder在支付完成進入出貨流程時建立
finalizeBuilder = new TransactionFinalizeBuilder();
if ("Cmuh".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project)) {
if (com.unibuy.smartdevice.AppEntry.isCmuh()) {
MyApp.getInstance().getReportFlowInfoData().setFlowType(42);
}
@ -311,7 +311,7 @@ public class DispatchDialog extends DialogAbstract {
getLogs().info("DispatchList size:" + this.dispatchList.size());
if ("Cmuh".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project)) {
if (com.unibuy.smartdevice.AppEntry.isCmuh()) {
try {
String patName = "未知患者";
String sn = "";
@ -529,7 +529,7 @@ public class DispatchDialog extends DialogAbstract {
R.string.check_whether_goods_have_been_taken_out_before_confirming_collection));// 請檢查商品有無取出再確認取貨
}
} else {
boolean isCmuh = "Cmuh".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project);
boolean isCmuh = com.unibuy.smartdevice.AppEntry.isCmuh();
int maxRetries = isCmuh ? 10 : 3;
if (checkSlotRunCount >= maxRetries) {
buttonControlCountdown.shutdown();
@ -940,7 +940,7 @@ public class DispatchDialog extends DialogAbstract {
isFinalizing = true;
getLogs().info("所有商品出貨完畢,發送 MQTT Finalize 結案訊息。");
if ("Cmuh".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project)) {
if (com.unibuy.smartdevice.AppEntry.isCmuh()) {
insertDrgnoToMemo();
}
publishFinalizeUnlessPassCode();
@ -1052,9 +1052,9 @@ public class DispatchDialog extends DialogAbstract {
addFailedDispenseRecordsFromCurrent();
if ("Cmuh".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project) && hasAnySuccessfulDispatch()) {
if (com.unibuy.smartdevice.AppEntry.isCmuh() && hasAnySuccessfulDispatch()) {
insertDrgnoToMemo();
} else if ("Cmuh".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project)) {
} else if (com.unibuy.smartdevice.AppEntry.isCmuh()) {
getLogs().info("CMUH 整單未出貨成功,不寫入 SN/DRGNO Memo允許重新掃碼領藥");
}

View File

@ -282,7 +282,10 @@ public class FontendPickupCodeDialog extends DialogAbstract {
// 進入頁面時立即檢查取貨口 (使用貨道1作為代表)
// callback null表示檢查通過後不做額外動作僅在檢查失敗時顯示警告
checkSlotHaveProduct(1, 1, null);
// pickup_sheet Cmuh流程不做取貨口檢查維持原行為
if (!"pickup_sheet".equals(MyApp.getInstance().getShoppingMode())) {
checkSlotHaveProduct(1, 1, null);
}
}
public class ReadBarCodeOnScheduler extends HandlerMainScheduler {
@ -313,7 +316,11 @@ public class FontendPickupCodeDialog extends DialogAbstract {
String barCodeString = new String(buffer, 0, size);
getHandlerMain().start(getLogs().getClassName(), getCtx().getString(R.string.receive_barcode)+""+ Tools.randomReplace(barCodeString, 0.3)+""+getCtx().getString(R.string.send_transaction));//收到條碼,送出交易
submitPickupCode(barCodeString.trim());
if ("pickup_sheet".equals(MyApp.getInstance().getShoppingMode())) {
submitViaMemberVerification(barCodeString.trim());
} else {
submitPickupCode(barCodeString.trim());
}
openBarcode = false;
}
@ -350,6 +357,11 @@ public class FontendPickupCodeDialog extends DialogAbstract {
public void changeOk() {
String number = binding.editTextNumber.getText().toString().trim();
// pickup_sheet Cmuh取貨碼走舊 memberVerification API維持原行為
if ("pickup_sheet".equals(MyApp.getInstance().getShoppingMode())) {
submitViaMemberVerification(number);
return;
}
// 長度檢查
if (number.isEmpty() || number.length() < 8) {
getHandlerMain().start(getClass().getSimpleName(), "取貨碼長度不能少於 8 碼");
@ -358,6 +370,19 @@ public class FontendPickupCodeDialog extends DialogAbstract {
submitPickupCode(number);
}
/** pickup_sheet 模式:取貨碼/條碼走舊 {@code httpAPI.memberVerification}(沿用 Cmuh 行為)。 */
private void submitViaMemberVerification(String code) {
try {
MyApp.getInstance().getMemberVerificationStructure().setProductId("0");
MyApp.getInstance().getMemberVerificationStructure().setProductPrice(0);
MyApp.getInstance().getMemberVerificationStructure().setMemberBarCode(code);
MyApp.getInstance().getMemberVerificationStructure().setVerificationType(2);
httpAPI.memberVerification(MyApp.getInstance().getMemberVerificationStructure());
} catch (LogsParseException | LogsSettingEmptyException e) {
getLogs().warning(e);
}
}
/**
* 取貨碼驗證改走 StarCloudAPI B660邏輯比照員工卡 B680
* 成功回傳 code_id 與要出貨的 slot_no據此建立 BuyList保存 code_id MQTT

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 732 B

After

Width:  |  Height:  |  Size: 732 B

View File

@ -152,6 +152,17 @@
android:visibility="gone"
android:textSize="28sp"
android:textColor="@color/black" />
<Button
android:id="@+id/buttonRetryEscrow"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:backgroundTint="#FF9800"
android:text="嘗試退鈔並重試"
android:visibility="gone"
android:textSize="28sp"
android:textColor="@color/white" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -14,4 +14,29 @@
android:background="@color/light_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- pickup_sheet原 Cmuh模式專用掃描單據按鈕其餘模式隱藏由 VmcFragment 依 shopping_mode 切換可見性)。 -->
<LinearLayout
android:id="@+id/layoutScanBarcode"
android:layout_width="match_parent"
android:layout_height="500dp"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/scanbarcodebtn"
android:layout_width="400dp"
android:layout_height="400dp"
android:layout_marginTop="-50dp"
android:padding="10dp"
android:text="掃描單據\n按鈕"
android:textColor="@android:color/white"
android:textSize="70sp"
app:strokeColor="@color/gray"
app:strokeWidth="5dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -401,4 +401,16 @@
<string name="cmuh_pickup_failed_contact_counter">Pickup failed\nPlease contact the counter</string>
<string name="close_40">Close(40)</string>
<string name="close_countdown">Close(%1$d)</string>
<!-- Multi-item shopping cart (merged from General flavor) -->
<string name="shopping_cart_title">Shopping Cart</string>
<string name="add_more_items">Add More Items</string>
<string name="go_to_pay">Proceed to Checkout</string>
<string name="cancel_button_format">Cancel (%d sec)</string>
<string name="total_items_format">Items: (%d)</string>
<string name="total_amount_format">NT$ %d</string>
<string name="cart_out_of_stock">Out of Stock</string>
<string name="cart_cannot_decrease">Cannot Decrease Further</string>
<string name="cart_max_items">Up to 8 items per cart</string>
<string name="cart_added">Added to cart</string>
<string name="data_error">Data Error</string>
</resources>

View File

@ -403,4 +403,16 @@
<string name="cmuh_pickup_failed_contact_counter">受け取り失敗\nカウンターへお問い合わせください</string>
<string name="close_40">閉じる(40)</string>
<string name="close_countdown">閉じる(%1$d)</string>
<!-- 多品項購物車merged from General flavor -->
<string name="shopping_cart_title">ショッピングカート</string>
<string name="add_more_items">商品を追加</string>
<string name="go_to_pay">お会計へ進む</string>
<string name="cancel_button_format">キャンセル(%d秒</string>
<string name="total_items_format">商品数:(%d)</string>
<string name="total_amount_format">NT$ %d</string>
<string name="cart_out_of_stock">在庫切れ</string>
<string name="cart_cannot_decrease">これ以上減らせません</string>
<string name="cart_max_items">カートは最大8点まで購入できます</string>
<string name="cart_added">カートに追加しました</string>
<string name="data_error">データ異常</string>
</resources>

View File

@ -1,25 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
機台功能開關 DevSet 的「初始預設值」首次開機、DB 尚無設定時套用)。
各 flavor 可於自身 res 覆寫main 此處維持原本寫死的預設,確保 Chengwai/Cmuh 行為不變。
實機上由主控台「系統設定」寫入後存 DB之後以 DB 值為準
專案維度已收斂為單一 App + 執行期 shopping_mode雲端 B014 下發),此處為「雲端配好前」的
bootstrap 預設,採 basic多品項購物車體驗雲端 B014 一到即覆蓋
-->
<resources>
<bool name="devset_default_vmc">true</bool>
<bool name="devset_default_electric">false</bool>
<bool name="devset_default_shopping_cart">false</bool>
<bool name="devset_default_shopping_cart">true</bool>
<bool name="devset_default_invoice">true</bool>
<bool name="devset_default_nfc_pay">true</bool>
<bool name="devset_default_esun_pay">true</bool>
<bool name="devset_default_tap_pay">true</bool>
<bool name="devset_default_cash">false</bool>
<bool name="devset_default_line_pay">false</bool>
<bool name="devset_default_cash">true</bool>
<bool name="devset_default_line_pay">true</bool>
<bool name="devset_default_tap_pay_30">true</bool>
<bool name="devset_default_tap_pay_31">true</bool>
<bool name="devset_default_tap_pay_32">true</bool>
<bool name="devset_default_tap_pay_33">true</bool>
<bool name="devset_default_tap_pay_34">true</bool>
<!-- 取貨碼/通行碼:改由雲端 B014 FunctionSet 下發控制bootstrap 預設開啟,雲端一到即覆蓋 -->
<bool name="devset_default_pickup_code">true</bool>
<bool name="devset_default_pass_code">true</bool>
<!-- 前台「取貨碼」浮鈕是否顯示(各 flavor 可覆寫main 預設隱藏,維持原行為) -->
<bool name="show_pickup_code_button">false</bool>
<!-- 前台「取貨碼」浮鈕舊資源(已改吃 DevSet.isPickupCode保留避免殘留引用編譯失敗 -->
<bool name="show_pickup_code_button">true</bool>
</resources>

View File

@ -320,4 +320,16 @@
<string name="cmuh_pickup_failed_contact_counter">取物失敗\n請洽櫃台</string>
<string name="close_40">關閉(40)</string>
<string name="close_countdown">關閉(%1$d)</string>
<!-- 多品項購物車(原 General flavor strings合併進 main -->
<string name="shopping_cart_title">購物車</string>
<string name="add_more_items">加購商品</string>
<string name="go_to_pay">前往結帳</string>
<string name="cancel_button_format">取消(%d秒</string>
<string name="total_items_format">商品數量:(%d)</string>
<string name="total_amount_format">NT$ %d</string>
<string name="cart_out_of_stock">超出庫存</string>
<string name="cart_cannot_decrease">不能減少了</string>
<string name="cart_max_items">購物車最多可購買 8 件商品</string>
<string name="cart_added">已加入購物車</string>
<string name="data_error">數據異常</string>
</resources>