[REFACTOR] 重構銷售流程至雙維度 Flavor 架構、統一忙碌狀態並修正版本規範

1. 引進 SaleFlowHandler 與 SaleFlowCallback 介面,分離通用版 (General)、晟崴版 (Chengwai) 與中國醫版 (Cmuh) 的前台銷售進入點,降低 FontendActivity 耦合度。
2. 建立 src/Chengwai/、src/Cmuh/、src/General/ 目錄,將客製 dialog(如 FontendPickupCodeDialog、TakeMedicineDialog)與 layout 移入對應的專屬目錄。
3. 統一狀態檢查機制,將全專案背景排程、定時重啟與日誌備份中的舊 isShowDialog() 替換為更安全的 isMachineBusy();於 NFC 交易與鍵盤 Enter 防誤觸中特意保留原 isShowDialog() 以守護安全性防線。
4. 修正英文 values-en/strings.xml 缺漏與預設 locale 對齊的 sync_product/sync_product_warning,解決 lintVital 報告的 ExtraTranslation 錯誤,使 Release 打包 100% 成功。
5. 微調 app/build.gradle,將起點版號重置為 10_01_1,自動連動 versionCode 100101 與 versionName 10_01_1_R。
6. 修正 .agents/rules/versioning.md 規範,將 S 修正為「來源 (Source)」,將 verMajor 修正為「螢幕大小」,並補齊官方命名規範對照圖表與欄位解析。
7. 升級一鍵編譯 /compiler-apk(新增中文與英文別名智慧防呆映射)與新建發版 /release-apk 工作流。
This commit is contained in:
sky121113 2026-05-27 14:48:22 +08:00
parent 6b3e05051f
commit 3409f93f07
38 changed files with 4238 additions and 48 deletions

View File

@ -13,7 +13,7 @@ trigger: always_on
> [!IMPORTANT]
> **開發人員與 AI 助手在修改版號時,嚴禁直接寫死 `versionCode``versionName`,必須透過修改 Gradle 頂部的變數來進行。**
* `verMajor` (大版號):代表重大架構變更或大型系統改版(例如 `10`)。
* **`verMajor` (螢幕大小)**:代表實體機台的螢幕吋數(例如 `10` 代表 10 吋螢幕)。
* `verMinor` (次版號):代表新增商業功能、硬體支援,但向下相容(例如 `8`)。
* `verPatch` (修補版號):代表 Bug 修復、小優化(例如 `5`)。
@ -23,7 +23,29 @@ trigger: always_on
---
## 2. 實體機台 OTA 自動更新鐵律
## 2. 公司官方命名規範對照
依據公司標準規範,實體機台發佈之 APK 與版本名稱需嚴格符合以下欄位對照:
```
來源 _ 安卓號 _ 主板號 _ 機器系列 _ 專案名稱 _ 螢幕大小 _ 次版號 _ 修補版號 _ 環境
S _ 12 _ XY _ U _ Chengwai _ 10 _ 08 _ 8 _ R
```
### 欄位解析:
* **`S` (來源 / Source)**:代表程式碼與專案來源(**非螢幕尺寸**)。
* **`12` (安卓號)**Android 系統主版本號。
* **`XY` (主板號)**:主板型號規格。
* **`U` (機器系列 / 機型規格)**:例如代表 U 系列機型。
* **`Chengwai` (專案名稱)**:客戶/銷售場景專案代碼(如 `General`, `Chengwai`, `Cmuh`)。
* **`10` (螢幕大小)**:對應 Gradle 中的 `verMajor`
* **`08` (次版號)**:對應 Gradle 中的 `verMinor`(格式化為雙位數)。
* **`8` (修補版號)**:對應 Gradle 中的 `verPatch`
* **`R` (環境)**:環境代碼,例如 `R` 代表 Release 生產環境。
---
## 3. 實體機台 OTA 自動更新鐵律
因為本 App 部署於實體販賣機,更新主要透過後台進行遠端自動下載覆蓋安裝 (OTA)。
@ -33,18 +55,34 @@ trigger: always_on
---
## 3. 機台套件與產品風味 (Product Flavors) 命名定義
## 4. 雙維度產品風味 (Product Flavors) 與 APK 命名規範
* **命名結構**`[螢幕尺寸]_[Android版本]_[主板與機型規格]`
* **現行標準 Flavor**`S_12_XY_U_Standard_`
* `S`:代表 Small 尺寸螢幕(一般 S 號)。
* `12`:代表針對 Android 12 系統優化之版本。
* `XY_U_Standard_`:代表 XY 主板、U 規格標準版。
* **擴充原則**:未來引進新硬體時,必須新增 Flavor 並遵循此命名格式(例如:`M_12_XY_U_Standard_`)。
本專案採用**雙維度 Flavor** 架構,以完美分離「客戶銷售場景」與「底層硬體規格」,方便後續快速擴充。
### 4.1 雙維度定義 (flavorDimensions)
本專案於 `app/build.gradle` 中定義了兩個 Flavor 維度:
1. **`project` 維度 (客戶/銷售場景區隔)**
* **`General`** (通用主幹):預設顯示付款方式選單(彈出 `BuyDialog`)。未來新客戶最常用此通用主幹。
* **`Chengwai`** (晟崴客製):直接跳出員工卡刷卡畫面(彈出 `MemberCardPickupDialog`),不顯示付款選單。
* **`Cmuh`** (中國醫客製):支援 QR Code 掃碼、處方籤醫令離線 AES 解密與解析領藥流程。
2. **`hardware` 維度 (硬體與系統規格區隔)**
* **`S12XYU`**:代表 `S` (來源) + `12` (安卓號) + `XY` (主板號) + `U` (機器系列/機型規格)。
### 4.2 實體機台 APK 命名鐵律
為了符合實體機台 OTA 平台發佈規範,打包輸出的 APK 檔名必須嚴格連動雙維度,並遵循以下格式:
`app-[格式化硬體規格]_[專案名稱]-[版本號]-[BuildType].apk`
* **格式化規則**`S12XYU` 會在 Gradle 打包時被自動展開格式化為 `S_12_XY_U`
* **輸出檔名範例**
* `app-S_12_XY_U_General-10_08_8_R-release.apk`
* `app-S_12_XY_U_Chengwai-10_08_8_R-release.apk`
* `app-S_12_XY_U_Cmuh-10_08_8_R-release.apk`
* **注意**:此檔名轉換規則已完全由 `app/build.gradle` 頂部 Gradle 變數與 `applicationVariants.all` 自動連動生成,**嚴禁手動修改輸出檔名**。
---
## 4. Git 發版配合流程
## 5. Git 發版配合流程
1. 自 `develop` 拉出 `release/10.8.5` 發版分支。
2. 遞增 `app/build.gradle` 中的 `verPatch` 變數並 Commit。

View File

@ -6,6 +6,12 @@ description: 自動編譯 Android APK、輸出至專屬目錄並安裝至實體
這個 Workflow 主要是協助您一鍵完成 Android 專案的編譯、匯出與安裝。當您呼叫 `/compiler-apk`AI 會自動為您執行以下步驟:
> [!TIP]
> **支援參數化指定編譯變體:**
> * 預設(不帶參數):編譯 **`Chengwai` (晟崴客製版)**。
> * 您可以呼叫 `/compiler-apk General`(或 `general`)編譯 **`General` (通用版)**。
> * 您可以呼叫 `/compiler-apk Cmuh`(或 `cmuh`)編譯 **`Cmuh` (中國醫客製版)**。
## 步驟零:自動檢查變更與升級版號
在開始編譯之前AI 助手會自動檢查是否有程式碼變更,以落實實體機台的 OTA 升級規範:
1. AI 助手將檢查 `git status --porcelain app/` 的輸出,以偵測是否有未提交的程式碼修改。
@ -13,7 +19,7 @@ description: 自動編譯 Android APK、輸出至專屬目錄並安裝至實體
3. 遞增完成後,向使用者呈報最新的版本號資訊,接著繼續執行步驟一。
## 步驟一:設定環境並編譯 Debug APK
使用 Java 17 與 Gradle 編譯專屬 Flavor (`S_12_XY_U_Standard_Debug`) 的程式碼。
使用 Java 17 與 Gradle 編譯對應 Flavor 與硬體規格的 Debug 程式碼。
```bash
// turbo
@ -23,9 +29,37 @@ description: 自動編譯 Android APK、輸出至專屬目錄並安裝至實體
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
# 取得目標 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
# 若非預設之中文別名,則嘗試做首字母大寫轉換(相容英文輸入,如 cmuh -> Cmuh
TARGET_FLAVOR=$(echo "$RAW_INPUT" | awk '{print toupper(substr($0,1,1))tolower(substr($0,2))}')
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}S12XYUDebug"
# 使用 clean 確保完整重新編譯,避免舊快取遮蔽 import 缺失等錯誤
# --no-daemon強制使用單次 JVM 進程,避免 Daemon 初始化期間被殘留訊號中斷
./gradlew clean :app:assembleS_12_XY_U_Standard_Debug --no-daemon
./gradlew clean :app:assemble${TARGET_FLAVOR}S12XYUDebug --no-daemon
```
## 步驟二:導出 APK 到 outputs 資料夾
@ -35,8 +69,26 @@ cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
// turbo
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
# 動態尋找編譯產出的最新 APK 檔案,避免因版號變更導致寫死路徑失效
APK_SOURCE=$(find app/build/outputs/apk/S_12_XY_U_Standard_/debug/ -name "app-S_12_XY_U_Standard_-*-debug.apk" | head -n 1)
APK_SOURCE=$(find app/build/outputs/apk/${TARGET_FLAVOR}S12XYU/debug/ -name "app-S_12_XY_U_${TARGET_FLAVOR}-*-debug.apk" | head -n 1)
if [ -n "$APK_SOURCE" ]; then
APK_NAME=$(basename "$APK_SOURCE")
@ -50,7 +102,7 @@ if [ -n "$APK_SOURCE" ]; then
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="S_12_XY_U_Standard_"
FLAVOR="${TARGET_FLAVOR}S12XYU"
echo "=================================================="
echo "📢 後台更新 keyin 參考資料:"
@ -73,12 +125,29 @@ 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
# 檢查是否有設備連接
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-S_12_XY_U_Standard_-*-debug.apk" | head -n 1)
APK_TARGET=$(find outputs/ -name "app-S_12_XY_U_${TARGET_FLAVOR}-*-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

@ -0,0 +1,117 @@
---
description: 自動編譯正式發行 Release APK、升級 Patch 版本號、並產出公司 OTA 平台必備之 Keyin 參考資料
---
# 生產環境發版打包工作流 (release-apk)
這個 Workflow 專門為應用程式「生產環境上線/發版」設計。當您準備好將特定客戶的最新功能派送至實體販賣機時,呼叫 `/release-apk` 將自動為您完成嚴格的發行檢查、版號遞增、Release 簽章打包與發版資訊生成。
> [!TIP]
> **支援參數化指定編譯變體:**
> * 預設(不帶參數):打包 **`Chengwai` (晟崴客製正式版)**。
> * 您可以呼叫 `/release-apk General`(或 `general`)打包 **`General` (通用正式版)**。
> * 您可以呼叫 `/release-apk Cmuh`(或 `cmuh`)打包 **`Cmuh` (中國醫客製正式版)**。
---
## 步驟零:發行檢查與版號自動遞增 (OTA 鐵律)
為了確保實體機台能夠順利觸發 OTA 自動下載覆蓋安裝,新編譯的 `versionCode` 必須嚴格大於現場的舊版號:
1. AI 助手將讀取 `app/build.gradle` 並自動將 `verPatch`(修補版號)**遞增 1**(例如從 `8` 自動升級為 `9`),確保 versionCode 順利變大。
2. 自動在 terminal 呈報即將打包的最新版本號資訊。
## 步驟一:清理快取並進行正式簽章編譯
採用單次 JVM 進程強制重新編譯,以確保代碼 100% 乾淨,並執行 Release 模式下的**代碼優化、混淆、與嚴格的 `lintVital` 審查**。
```bash
// turbo
# 終止殘留的 Gradle Daemon 程序,避免多個 Daemon 互搶資源,同時避免誤殺 VS Code Gradle 擴充功能
./gradlew --stop 2>/dev/null; pkill -9 -f "org.gradle.launcher.daemon.bootstrap.GradleDaemon" 2>/dev/null; sleep 3
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))}')
fi
# 安全防護:檢查是否為支援的客製項目
if [ "$TARGET_FLAVOR" != "General" ] && [ "$TARGET_FLAVOR" != "Chengwai" ] && [ "$TARGET_FLAVOR" != "Cmuh" ]; then
echo "❌ 不支援的 Flavor: $TARGET_FLAVOR請使用 General, Chengwai 或 Cmuh。"
exit 1
fi
echo "📦 [Release] 開始編譯正式版變體: ${TARGET_FLAVOR}S12XYURelease"
# 使用 clean 確保完整重新編譯,避開 incremental 快取問題
./gradlew clean :app:assemble${TARGET_FLAVOR}S12XYURelease --no-daemon
```
## 步驟二:導出正式 APK 並產出後台 Keyin 派送參考資料
將封裝完畢、具備正式簽章的 APK 導出至最外層 `outputs/` 資料夾,並自動計算產出公司 OTA 派送後台所需的全部對照資料。
```bash
// turbo
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))}')
fi
# 動態尋找編譯產出的最新正式版 APK 檔案
APK_SOURCE=$(find app/build/outputs/apk/${TARGET_FLAVOR}S12XYU/release/ -name "app-S_12_XY_U_${TARGET_FLAVOR}-*-release.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 "=================================================="
echo "✅ 正式版 APK 封裝成功,已導出至:$APK_DEST"
echo "=================================================="
# 提取與計算版本資訊
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}')
VERSION_NAME="${MAJOR}_0${MINOR}_${PATCH}_R"
VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH))
FLAVOR="${TARGET_FLAVOR}S12XYU"
echo "🚀 實體販賣機 OTA 後台新增版本 Keyin 參考資料:"
echo "--------------------------------------------------"
echo "1. 專案/客戶風味 (Flavor) : $FLAVOR"
echo "2. 版本代碼 (versionCode) : $VERSION_CODE"
echo "3. 版本名稱 (versionName) : $VERSION_NAME"
echo "4. 螢幕吋數 (Screen Size) : ${MAJOR} 吋"
echo "5. 正式版 APK 檔名 : $APK_NAME"
echo "--------------------------------------------------"
echo "📢 [溫馨提醒] 請將此 APK 上傳至 OTA 平台,並設定為對應機型的強迫升級。"
echo "=================================================="
else
echo "❌ 找不到編譯產出的正式版 APK 檔案,封裝失敗!"
exit 1
fi
```
## 步驟三:自動化 Git 發版標記引導
為了落實公司發版規範第 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}"
echo "=================================================="
echo "🔔 Git 發版追溯標記指南"
echo "--------------------------------------------------"
echo "請在專案合併回 main 分支後,執行以下指令打上發版 Tag"
echo "git tag -a $TAG_NAME -m \"TaiwanStar 發版 - 版本 $TAG_NAME\""
echo "git push origin $TAG_NAME"
echo "=================================================="
```

View File

@ -9,8 +9,8 @@ plugins {
// versionCode 100806 OTA
// =========================================================================
def verMajor = 10
def verMinor = 8
def verPatch = 8
def verMinor = 1
def verPatch = 1
android {
namespace 'com.unibuy.smartdevice'
@ -26,14 +26,26 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
//
flavorDimensions "app_type"
// =====================================================================
// Flavorproject× hardware
// =====================================================================
flavorDimensions "project", "hardware"
productFlavors {
S_12_XY_U_Standard_ {
dimension "app_type"
versionCode verMajor * 10000 + verMinor * 100 + verPatch
versionName "${verMajor}_0${verMinor}_${verPatch}_R"
General {
dimension "project"
}
Chengwai {
dimension "project"
}
Cmuh {
dimension "project"
}
S12XYU {
dimension "hardware"
}
}
@ -45,16 +57,20 @@ android {
}
}
//
// _安卓號_主板號_機器系列_專案名稱_螢幕大小_次版號_修補版號_環境
applicationVariants.all { variant ->
variant.outputs.each { output ->
def versionCode = variant.versionCode
def versionName = variant.versionName
def flavor = variant.productFlavors[0].name
def project = variant.productFlavors[0].name // project dimension
def hardware = variant.productFlavors[1].name // hardware dimension
def buildType = variant.buildType.name
// app-<flavor>-<versionName>-<buildType>.apk
output.outputFileName = "app-${flavor}-${versionName}-${buildType}.apk"
// hardware flavor S12XYU S_12_XY_U
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}-${versionName}-${buildType}.apk"
}
}

View File

@ -0,0 +1,38 @@
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 onBuyRequested() {
new MemberCardPickupDialog(context, handlerMain).show();
}
public void onBarcodeReceived(String rawData) {
// 晟崴版不處理掃碼
}
public void onPickupRequested() {
// 晟崴版不處理取貨碼
}
}

View File

@ -0,0 +1,79 @@
package com.unibuy.smartdevice.structure;
public class MedicineStructure {
String MaterialCode;
int MedicineCount;
String MedicineName;
String MedicineSlot;
String MedicineImg;
String productId;
String MemoSnDrgno;
// 建構子
public MedicineStructure(String MaterialCode, int MedicineCount, String MedicineName, String MedicineSlot, String MedicineImg, String productId, String MemoSnDrgno) {
this.MaterialCode = MaterialCode;
this.MedicineCount = MedicineCount;
this.MedicineName = MedicineName;
this.MedicineSlot = MedicineSlot;
this.MedicineImg = MedicineImg;
this.productId = productId;
this.MemoSnDrgno = MemoSnDrgno;
}
public String getMaterialCode() {
return MaterialCode;
}
public void setMaterialCode(String MaterialCode) {
this.MaterialCode = MaterialCode;
}
public int getMedicineCount() {
return MedicineCount;
}
public void setMedicineCount(int MedicineCount) {
this.MedicineCount = MedicineCount;
}
public String getMedicineName() {
return MedicineName;
}
public void setMedicineName(String MedicineName) {
this.MedicineName = MedicineName;
}
public String getMedicineSlot() {
return MedicineSlot;
}
public void setMedicineSlot(String MedicineSlot) {
this.MedicineSlot = MedicineSlot;
}
public String getMedicineImg() {
return MedicineImg;
}
public void setMedicineImg(String MedicineImg) {
this.MedicineImg = MedicineImg;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getMemoSnDrgno() {
return MemoSnDrgno;
}
public void setMemoSnDrgno(String MemoSnDrgno) {
this.MemoSnDrgno = MemoSnDrgno;
}
}

View File

@ -0,0 +1,387 @@
package com.unibuy.smartdevice.ui;
import android.app.Activity;
import android.content.Context;
import android.util.Base64;
import android.view.View;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.devices.DeviceType;
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.LogsUnsupportedOperationException;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.structure.MedicineStructure;
import com.unibuy.smartdevice.structure.ProductStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.HandlerMainSchedulerOnClick;
import com.unibuy.smartdevice.ui.dialog.FontendPickupCodeDialog;
import com.unibuy.smartdevice.ui.dialog.PickupErrorTipDialog;
import com.unibuy.smartdevice.ui.dialog.TakeMedicineDialog;
import com.unibuy.smartdevice.ui.devs.vmc.VmcFragment;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
* Cmuh (中國醫版) 銷售流程處理器
* 負責
* 1. 綁定40秒內刷條碼按鈕 (scanbarcodebtn)
* 2. 連接 USB 掃描器背景讀取
* 3. 解密與解析醫令 (AES 128 ECB)
* 4. 庫存核對與自動加入購物車
* 5. 顯示領藥與錯誤 Dialog
*/
public class SaleFlowHandler {
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;
}
/**
* Activity onResume() 中會被呼叫此時尋找並綁定 VmcFragment 中的掃碼按鈕
*/
public void onSetupUI() {
if (context instanceof Activity) {
View btnScan = ((Activity) context).findViewById(R.id.scanbarcodebtn);
if (btnScan != null) {
btnScan.setOnClickListener(new BtnQrCodeOnClick(handlerMain, context));
}
}
}
public void onBuyRequested() {
// 中國醫版沒有購物車付款此方法不執行
}
public void onBarcodeReceived(String rawData) {
// 若是經由其他方式傳入的 Barcode 可在此處直接處理
try {
barcodeAESDecryption(rawData);
} catch (Exception e) {
if (callback != null) {
callback.onSaleFlowError("掃碼處理失敗:" + e.getMessage());
}
}
}
/**
* CMUH 點選取貨按鈕開啟輸入取貨碼的 Dialog
*/
public void onPickupRequested() {
new FontendPickupCodeDialog(context, handlerMain).show();
}
// 內部掃碼 Button Click 監聽器與背景 Task
private class BtnQrCodeOnClick extends HandlerMainSchedulerOnClick {
private final Context context;
public BtnQrCodeOnClick(HandlerMain srcHandlerMain, Context context) {
super(srcHandlerMain);
this.context = context;
}
@Override
protected HandlerMain setHandlerMain() {
return getSrcHandlerMain();
}
@Override
protected void execute(HandlerMain handlerMain) {
handlerMain.start(getClass().getSimpleName(), "@40秒內刷條碼");
openBarcode = true;
try {
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
if (size > 5) {
if (!openBarcode) continue;
openBarcode = false; // 立即標記防止重複進入解碼邏輯
MyApp.getInstance().getLogs().info("✅ 觸發掃碼源頭防呆,此筆掃碼僅處理一次");
String barCodeString = new String(buffer, 0, size).trim();
if (barCodeString.isEmpty()) {
openBarcode = true;
continue;
}
MyApp.getInstance().getLogs().info("掃描成功: [" + barCodeString + "]");
try {
barcodeAESDecryption(barCodeString);
} catch (LogsParseException e) {
MyApp.getInstance().getLogs().warning(e);
handlerMain.send(getClass().getSimpleName(), VmcFragment.Option.DECRYPTION_ERROR.getOption(), e.getLocalizedMessage());
openBarcode = true;
return;
}
if (haveValuestate) {
((Activity) context).runOnUiThread(() -> new TakeMedicineDialog(context, handlerMain).show());
} else {
((Activity) context).runOnUiThread(() -> new PickupErrorTipDialog(context, handlerMain).show());
openBarcode = true;
}
break; // 處理完成跳出掃描迴圈
}
} else {
break;
}
}
usbDev.closePort();
} catch (LogsUnsupportedOperationException | LogsIOException | LogsEmptyException e) {
MyApp.getInstance().getLogs().warning(e);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public void onClick(View v) {
if (System.currentTimeMillis() - lastClickTime < 3000) {
MyApp.getInstance().getLogs().info("⚠️ 點擊太快,觸發防呆");
return;
}
lastClickTime = System.currentTimeMillis();
setRun(true);
start();
}
@Override
public boolean onLongClick(View v) {
return false;
}
@Override
protected Class<?> setCls() {
return getClass();
}
}
// 醫令 AES 解密與解析庫存邏輯
private boolean barcodeAESDecryption(String scanBarcode) throws JSONException, LogsParseException {
try {
String keyString = "cmuhch@mis@drug1"; // 16字節的 key
String encryptedBase64 = scanBarcode;
if (encryptedBase64 == null || encryptedBase64.isEmpty()) {
throw new IllegalArgumentException("錯誤:加密數據為 null 或空,無法解密");
}
byte[] encryptedBytes = Base64.decode(encryptedBase64, Base64.DEFAULT);
if (encryptedBytes == null || encryptedBytes.length == 0) {
throw new IllegalArgumentException("錯誤Base64 解碼後的數據為 null 或空");
}
if (encryptedBytes.length % 16 != 0) {
throw new IllegalArgumentException("錯誤:加密數據長度不是 16 的倍數,可能需要填充");
}
byte[] keyBytes = keyString.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); // 無填充模式
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
MyApp.getInstance().setDecryptedData("");
MyApp.getInstance().setDecryptedData(new String(decryptedBytes, StandardCharsets.UTF_8).trim());
String decryptedStr = MyApp.getInstance().getDecryptedData();
MyApp.getInstance().getLogs().info("decryptedStr=" + decryptedStr);
JSONObject jsonObject = new JSONObject(decryptedStr);
addMedicineToByulist(jsonObject);
checkORDCDispatchNumber(decryptedStr); // 判斷領藥號是否已經領過
} catch (Exception e) {
throw new LogsParseException(MyApp.getInstance().getLogs(), "錯誤:加密數據長度不是 16 的倍數,可能需要填充");
}
return haveValuestate;
}
public void addMedicineToByulist(JSONObject jsonObject) {
JSONArray itemsArray = null;
try {
itemsArray = jsonObject.getJSONArray("ITEM");
// 清空 BuyList確保每次掃描都是新的出貨清單
MyApp.getInstance().getBuyList().clear();
int MedicineCount = 0; // 同一種商品全部貨道的總數量
String MedicineName = "", MedicineSlot = "", MedicineImg = "", MedicinePId = "", MemoSnDrgno = "";
ArrayList<MedicineStructure> medicineList = new ArrayList<>();
// 先進行預判確認所有 ITEM 都能成功出貨才繼續後面流程
boolean stockCheckPassed = true; // 預設檢查通過
for (int i = 0; i < itemsArray.length(); i++) {
JSONObject itemObject = itemsArray.getJSONObject(i);
String ordc = itemObject.optString("ORDC");
int requestQty = Integer.parseInt(itemObject.optString("QTY"));
int totalStock = 0;
for (int j = 0; j < MyApp.getInstance().getSlotList(0).size(); j++) {
if (ordc.equals(MyApp.getInstance().getSlotList(0).get(j).getProduct().getMaterialCode())
&& MyApp.getInstance().getSlotList(0).get(j).getCount() > 0
&& MyApp.getInstance().getSlotList(0).get(j).getCount() <= 20) {
totalStock += MyApp.getInstance().getSlotList(0).get(j).getCount();
}
}
if (totalStock == 0) {
MyApp.getInstance().getLogs().info("預判失敗ORDC = " + ordc + " 找不到貨道或庫存為0");
String storename = "@商品 [" + ordc + "] 無庫存或未設定貨道,請確認。";
handlerMain.start("無庫存或未設定貨道,請確認", storename);
stockCheckPassed = false;
break;
} else if (totalStock < requestQty) {
MyApp.getInstance().getLogs().info("預判失敗ORDC = " + ordc + " 庫存不足,總庫存:" + totalStock + ",需求:" + requestQty);
String storename = "@商品 [" + ordc + "] 庫存不足,請補貨後再掃描。";
handlerMain.start("庫存不足", storename);
stockCheckPassed = false;
break;
}
}
if (!stockCheckPassed) {
return; // 直接結束不進入後續流程
}
MyApp.getInstance().getLogs().info("itemsArray.length" + itemsArray.length());
for (int i = 0; i < itemsArray.length(); i++) {
JSONObject itemObject = itemsArray.getJSONObject(i);
// 重新初始化這些變數
MedicineName = "";
MedicineCount = 0;
MedicineSlot = "";
MedicineImg = "";
MedicinePId = "";
MemoSnDrgno = "";
for (int j = 0; j < MyApp.getInstance().getSlotList(0).size(); j++) {
if (itemObject.optString("ORDC").equals(MyApp.getInstance().getSlotList(0).get(j).getProduct().getMaterialCode())
&& MyApp.getInstance().getSlotList(0).get(j).getCount() > 0
&& MyApp.getInstance().getSlotList(0).get(j).getCount() <= 20) {
MedicineCount += MyApp.getInstance().getSlotList(0).get(j).getCount();
MedicineSlot += MyApp.getInstance().getSlotList(0).get(j).getSlot() + "-" + MyApp.getInstance().getSlotList(0).get(j).getCount() + ",";
if (MedicineCount >= Integer.valueOf(itemObject.optString("QTY"))) {
MedicinePId = MyApp.getInstance().getSlotList(0).get(j).getProduct().getProductID();
MedicineName = MyApp.getInstance().getSlotList(0).get(j).getProduct().getProductName();
MedicineImg = MyApp.getInstance().getSlotList(0).get(j).getProduct().getProductImg();
MemoSnDrgno = "{\"SN\": \"" + jsonObject.optString("SN") + "\", \"DRGNO\": \"" + jsonObject.optString("DRGNO") + "\"}";
}
}
}
medicineList.add(new MedicineStructure(itemObject.optString("ORDC"), MedicineCount, MedicineName, MedicineSlot, MedicineImg, MedicinePId, MemoSnDrgno));
}
/* 這裡將商品依據領藥數量加入 BuyList */
for (int i = 0; i < itemsArray.length(); i++) {
JSONObject itemObject = itemsArray.getJSONObject(i);
int requestQty = Integer.parseInt(itemObject.optString("QTY"));
if (requestQty <= medicineList.get(i).getMedicineCount()) {
haveValuestate = true;
if (requestQty > 1) {
int checkcount = 0;
int slotnum = 0;
for (int j = 0; j < requestQty; j++) {
checkcount += 1;
if (checkcount > Integer.valueOf(medicineList.get(i).getMedicineSlot().split(",")[0].split("-")[1])) {
slotnum = Integer.valueOf(medicineList.get(i).getMedicineSlot().split(",")[1].split("-")[0]);
} else {
slotnum = Integer.valueOf(medicineList.get(i).getMedicineSlot().split(",")[0].split("-")[0]);
}
addMedicineToBuyList(
medicineList.get(i).getProductId(),
medicineList.get(i).getMedicineName(),
slotnum,
medicineList.get(i).getMedicineImg(),
medicineList.get(i).getMemoSnDrgno(),
1
);
}
} else {
addMedicineToBuyList(
medicineList.get(i).getProductId(),
medicineList.get(i).getMedicineName(),
Integer.valueOf(medicineList.get(i).getMedicineSlot().split(",")[0].split("-")[0]),
medicineList.get(i).getMedicineImg(),
medicineList.get(i).getMemoSnDrgno(),
1
);
}
} else {
if (medicineList.get(i).getMedicineCount() < 1) {
handlerMain.start("這裡判斷貨道數量不足提示警語", "@領藥單有貨道數量問題,請洽藥局發藥窗口!");
MyApp.getInstance().getBuyList().clear();
}
}
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
public void addMedicineToBuyList(String MedicinePidStr, String MedicineNameStr, int SlotNumber, String MedicineImg, String MemoSnDrgno, int count) {
ProductStructure productStructure = new ProductStructure(MedicinePidStr, MedicineImg, MedicineNameStr, 0, 0, 0, 0, MemoSnDrgno, "", "", "");
MyApp.getInstance().getLogs().info("aaaaaa " + MedicinePidStr + " " + MemoSnDrgno + " " + MedicineNameStr);
BuyStructure buyProduct = new BuyStructure(0, SlotNumber, productStructure, count);
MyApp.getInstance().getBuyList().add(buyProduct);
}
public void checkORDCDispatchNumber(String decryptedStr) throws JSONException {
JSONObject jsonObject = new JSONObject(decryptedStr);
JSONObject newItem = new JSONObject();
newItem.put("SN", jsonObject.optString("SN"));
newItem.put("DRGNO", jsonObject.optString("DRGNO"));
String jsonStr = MyApp.getInstance().getMemoMap().get("CMUH").get(0).getData().toString();
JSONObject jsonObject2 = new JSONObject(jsonStr);
JSONArray itemArray = jsonObject2.getJSONArray("ITEM");
boolean exists = false;
for (int i = 0; i < itemArray.length(); i++) {
JSONObject item = itemArray.getJSONObject(i);
if (item.getString("SN").equals(newItem.getString("SN")) && item.getString("DRGNO").equals(newItem.getString("DRGNO"))) {
exists = true;
break;
}
}
if (exists) {
MyApp.getInstance().getLogs().info("⚠️ 此項目已存在,不新增!");
openBarcode = false;
handlerMain.start(getClass().getSimpleName(), "@此單已取物完成,請洽藥局發藥窗口!");
MyApp.getInstance().getBuyList().clear();
}
}
}

View File

@ -0,0 +1,263 @@
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

@ -0,0 +1,429 @@
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

@ -0,0 +1,175 @@
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.DialogPickupErrorTipBinding;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.structure.ProductStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.HandlerMainCountdown;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
public class PickupErrorTipDialog 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 PickupErrorTipDialog(Context context, HandlerMain srcHandlerMain) {
super(context, srcHandlerMain);
getLogs().info("PickupErrorTipDialog:" + getClass().getSimpleName());
}
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 this.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:
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 DialogPickupErrorTipBinding binding;
//private RecyclerDialogTakeMedicineListAdpter recyclerDialogTakeMedicineListAdpter;
private CloseDialogOnThreadHandler closeDialogCountdown;
@Override
protected void onCreate(Context context) {
binding = DialogPickupErrorTipBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
String decryptedStr = MyApp.getInstance().getDecryptedData();
getLogs().info(decryptedStr);
try {
JSONObject jsonObject = new JSONObject(decryptedStr);
binding.tvDrgnoid.setText("領藥號:"+jsonObject.optString("DRGNO"));
} catch (JSONException e) {
getLogs().warning(e);
}
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
closeDialogCountdown.start(40);
binding.btnNonPerson.setOnClickListener(v -> dialogCancel());
}
public void addMedicineToBuyList(String MedicinePidStr,String MedicineNameStr,int SlotNumber,String MedicineImg){
ProductStructure productStructure = new ProductStructure(MedicinePidStr,MedicineImg,MedicineNameStr,0, 0, 0, 0, "", "", "", "");
//getLogs().info("aaaa"+SlotNumber);
BuyStructure buyProduct = new BuyStructure(0, SlotNumber, productStructure, 1);
MyApp.getInstance().getBuyList().add(buyProduct);
}
public void dialogCancel() {
closeDialogCountdown.shutdown();
/*int buyListSize = MyApp.getInstance().getBuyList().size();
MyApp.getInstance().getBuyList().clear();
if (buyListSize > 0) {
recyclerDialogTakeMedicineListAdpter.notifyItemRangeRemoved(0, buyListSize);
}*/
cancel();
}
public void cancelOnThreadCountdown() {
/*int buyListSize = MyApp.getInstance().getBuyList().size();
MyApp.getInstance().getBuyList().clear();
if (buyListSize > 0) {
recyclerDialogTakeMedicineListAdpter.notifyItemRangeRemoved(0, buyListSize);
}*/
getTools().dialogClose();
cancel();
}
public void setButtonCancelText(long countdown) {
binding.btnNonPerson.setText("否("+countdown+")");
}
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

@ -0,0 +1,273 @@
package com.unibuy.smartdevice.ui.dialog;
import android.content.Context;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.databinding.DialogCmuhTakeMedicineBinding;
import com.unibuy.smartdevice.external.HttpAPI;
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.RecyclerDialogTakeMedicineListAdpter;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
public class TakeMedicineDialog extends DialogAbstract {
public enum Option {
TOAST(0),
COUNTDOWN_CANCEL(1),
SET_COUNTDOWN_TEXT(2),
SET_PRICE(3),
RESET_COUNTDOWM(4),
RESET_COUNTDOWM_TOAST(5),
CHANGE_DISPATCH(6),
REPORT_FLOW(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();
}
private boolean isChangeDispatch;
private boolean isRun;
boolean isResponse;
private HttpAPI httpAPI;
@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) {
getSrcHandlerMain().start("這裡會出現貨道無貨提示警語","@再刷一次,若不行,請洽發藥窗口!");
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:
closeDialogCountdown.setCountdown(40);
break;
case RESET_COUNTDOWM:
closeDialogCountdown.setCountdown(40);
break;
case RESET_COUNTDOWM_TOAST:
super.execute(context, commandCode, message);
closeDialogCountdown.setCountdown(40);
break;
case CHANGE_DISPATCH:
changeDispatch();
break;
case REPORT_FLOW:
getLogs().info(MyApp.getInstance().getReportFlowInfoData().toString());
if (MyApp.getInstance().getReportFlowInfoData().getFlowStatusType() == 0) {
start(getSrcClassName(), CheckoutCashDialog.Option.CHANGE_BUY.getOption(), "change buy");
} else {
if (MyApp.getInstance().getReportFlowInfoData().getInvoiceInfo().isEmpty()) {
start(getSrcClassName(), CheckoutCashDialog.Option.CHANGE_DISPATCH.getOption(), "change dispatch");
} else {
start(getSrcClassName(), CheckoutCashDialog.Option.POST_INVOICE.getOption(), "post invoice");
}
}
// new HandlerMainCountdown(this) {
// @Override
// protected Class<?> setCls() {
// return this.getClass();
// }
//
// @Override
// protected HandlerMain setHandlerMain() {
// return getSrcHandlerMain();
// }
//
// @Override
// protected void execute(long countdown, HandlerMain handlerMain) {
//
// }
//
// @Override
// protected void close(HandlerMain handlerMain) {
// if (MyApp.getInstance().getReportFlowInfoData().getFlowStatusType() == 0) {
// handlerMain.start(getSrcClassName(), CheckoutCashDialog.Option.CHANGE_BUY.getOption(), "change buy");
// } else {
// if (MyApp.getInstance().getReportFlowInfoData().getInvoiceInfo().isEmpty()) {
// handlerMain.start(getSrcClassName(), CheckoutCashDialog.Option.CHANGE_DISPATCH.getOption(), "change dispatch");
// } else {
// handlerMain.start(getSrcClassName(), CheckoutCashDialog.Option.POST_INVOICE.getOption(), "post invoice");
// }
// }
// }
// }.start(2);
break;
}
}
}
};
}
private DialogCmuhTakeMedicineBinding binding;
private RecyclerDialogTakeMedicineListAdpter recyclerDialogTakeMedicineListAdpter;
private CloseDialogOnThreadHandler closeDialogCountdown;
public TakeMedicineDialog(Context context, HandlerMain srcHandlerMain) {
super(context, srcHandlerMain);
getLogs().info("open:" + getClass().getSimpleName());
}
@Override
protected void onCreate(Context context) {
binding = DialogCmuhTakeMedicineBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
this.isChangeDispatch = true;
this.isRun = true;
isResponse = true;
this.httpAPI = new HttpAPI(getHandlerMain());
this.recyclerDialogTakeMedicineListAdpter = new RecyclerDialogTakeMedicineListAdpter(getCtx(), MyApp.getInstance().getBuyList(), getHandlerMain());
this.binding.recyclerDialogBuyList.setAdapter(recyclerDialogTakeMedicineListAdpter);
this.binding.recyclerDialogBuyList.setLayoutManager(new LinearLayoutManager(context));
MyApp.getInstance().initialReportData();
MyApp.getInstance().getReportFlowInfoData().setOrderId(Tools.generateTimeBasedUUID());
MyApp.getInstance().getPostInvoiceByGreenData().setOrderId(MyApp.getInstance().getReportFlowInfoData().getOrderId());
String decryptedStr = MyApp.getInstance().getDecryptedData();
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(decryptedStr);
} catch (JSONException e) {
throw new RuntimeException(e);
}
binding.tvHostId.setText("管理單位ID"+jsonObject.optString("HOSP_ID"));
binding.tvSn.setText("序號:"+jsonObject.optString("SN"));
binding.tvName.setText("姓名:"+jsonObject.optString("NAME"));
binding.tvDrgno.setText("領藥號:"+jsonObject.optString("DRGNO"));
binding.tvData.setText("備註:"+jsonObject.optString("DATA"));
binding.tvPrintTime.setText("列印時間:"+jsonObject.optString("PRINT_TIME"));
this.binding.buttonAddProduct.setOnClickListener(v -> gotoAddProduct());
//this.binding.buttonCancel.setOnClickListener(v -> dialogCancel());
closeDialogCountdown = new CloseDialogOnThreadHandler(getHandlerMain());
closeDialogCountdown.start(40);
// if (MyApp.getInstance().getDevSet().isShoppingCar()) {
// binding.buttonAddProduct.setVisibility(View.VISIBLE);
// } else {
// binding.buttonAddProduct.setVisibility(View.GONE);
// }
}
public void gotoAddProduct() {
//getSrcHandlerMain().start(getClass().getSimpleName(), FontendActivity.Option.ADD_PRODUCT.getOption(), "請在40秒內完成加購動作");
changeDispatch();
closeDialogCountdown.shutdown();
cancel();
}
public void changeDispatch() {
if (isChangeDispatch){
isRun = false;
isChangeDispatch = false;
//closeMoneyType();
closeDialogCountdown.shutdown();
changeDialog(DispatchDialog.class);
}
}
public void dialogCancel() {
closeDialogCountdown.shutdown();
int buyListSize = MyApp.getInstance().getBuyList().size();
MyApp.getInstance().getBuyList().clear();
if (buyListSize > 0) {
recyclerDialogTakeMedicineListAdpter.notifyItemRangeRemoved(0, buyListSize);
}
cancel();
}
public void cancelOnThreadCountdown() {
int buyListSize = MyApp.getInstance().getBuyList().size();
MyApp.getInstance().getBuyList().clear();
if (buyListSize > 0) {
recyclerDialogTakeMedicineListAdpter.notifyItemRangeRemoved(0, buyListSize);
}
getTools().dialogClose();
cancel();
}
public void setButtonCancelText(long countdown) {
binding.buttonCancel.setText("取消("+countdown+")");
}
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

@ -0,0 +1,193 @@
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));
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

@ -0,0 +1,91 @@
package com.unibuy.smartdevice.ui.recycler;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.unibuy.smartdevice.databinding.RecyclerDialogTakeMedicineListBinding;
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.tools.ImageGlide;
import java.util.List;
public class RecyclerDialogTakeMedicineListAdpter extends RecyclerView.Adapter<RecyclerDialogTakeMedicineListAdpter.ViewHolder> {
private Logs logs;
private Context context;
private final List<BuyStructure> buyList;
private HandlerMain handlerMain;
public RecyclerDialogTakeMedicineListAdpter(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 {
RecyclerDialogTakeMedicineListBinding binding;
public ViewHolder(@NonNull RecyclerDialogTakeMedicineListBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 使用 ViewBinding 來綁定佈局
RecyclerDialogTakeMedicineListBinding binding = RecyclerDialogTakeMedicineListBinding.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("點我選擇商品");
}
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 count = buyProduct.getCount();
holder.binding.textCount.setText("x"+String.valueOf(count));
logs.info("field:" + buyProduct.getField());
try {
SlotField slotField = SlotField.getSlotField(buyProduct.getField());
logs.info("slotField:" + slotField.isCanAddPurchased());
} catch (LogsEmptyException e) {
}
}
@Override
public int getItemCount() {
return buyList.size();
}
}

View File

@ -0,0 +1,198 @@
<?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/linearLayout1"
android:layout_width="match_parent"
android:layout_height="360dp"
android:orientation="vertical"
android:gravity="center"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="50dp"
tools:ignore="MissingConstraints">
<TextView
android:id="@+id/tv_host_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="100dp"
/>
<TextView
android:id="@+id/tv_sn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="100dp"
/>
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="100dp"
/>
<TextView
android:id="@+id/tv_drgno"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="100dp"
/>
<TextView
android:id="@+id/tv_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="100dp"
/>
<TextView
android:id="@+id/tv_print_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="100dp"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout112"
android:layout_width="match_parent"
android:layout_height="10dp"
android:gravity="center"
android:orientation="horizontal"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
app:layout_constraintTop_toBottomOf="@id/linearLayout1"
app:layout_constraintStart_toStartOf="parent">
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#000000"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout1111"
android:layout_width="match_parent"
android:layout_height="150dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/linearLayout112"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="請確認領藥人的訊息內容是否正確\n再按下「確定取貨」按鈕"
android:textSize="36dp"
android:gravity="center"
android:textAlignment="center"/>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/light_gray"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@+id/linearLayout1111"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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="30sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerDialogBuyList"
android:layout_width="match_parent"
android:layout_height="750dp"
/>
<LinearLayout
android:id="@+id/layoutNfcPay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
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:visibility="gone"
android:text="取消(40)"
android:textSize="30sp"
android:textColor="@color/black" />
<Button
android:id="@+id/buttonAddProduct"
android:layout_width="wrap_content"
android:layout_height="120dp"
android:layout_weight="1"
android:backgroundTint="@color/white"
android:text="確定取貨"
android:textColor="@color/black"
android:textSize="60sp"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,104 @@
<?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

@ -0,0 +1,194 @@
<?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

@ -0,0 +1,84 @@
<?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">
<LinearLayout
android:id="@+id/linearLayout211"
android:layout_width="match_parent"
android:layout_height="150dp"
android:gravity="center"
android:orientation="horizontal"
tools:ignore="MissingConstraints">
<TextView
android:id="@+id/tv_drgnoid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="領藥號:"
android:textSize="60dp"
android:layout_gravity="bottom"
android:gravity="center"
android:textAlignment="center"/>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout212"
android:layout_width="match_parent"
android:layout_height="300dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@+id/linearLayout211"
tools:ignore="MissingConstraints">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取物失敗\n請洽櫃台\n#104"
android:textSize="60dp"
android:gravity="center"
android:textAlignment="center"/>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout1121"
android:layout_width="match_parent"
android:layout_height="10dp"
android:gravity="center"
android:orientation="horizontal"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
app:layout_constraintTop_toBottomOf="@id/linearLayout212"
app:layout_constraintStart_toStartOf="parent">
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#B0B0B0"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:orientation="horizontal"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
app:layout_constraintTop_toBottomOf="@id/linearLayout1121"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/btn_non_person"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="否(40)"
android:textSize="30sp"
tools:ignore="MissingConstraints" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,40 @@
<?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:orientation="horizontal"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
>
<Button
android:id="@+id/scanbarcodebtn"
android:layout_width="400dp"
android:layout_height="400dp"
android:padding="10dp"
android:text="掃描單據\n按鈕"
android:textSize="70sp"
android:textColor="@android:color/white"
app:strokeWidth="5dp"
app:strokeColor="@color/gray"
android:layout_marginTop="-50dp"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,109 @@
<?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

@ -0,0 +1,102 @@
<?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/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

@ -0,0 +1,38 @@
package com.unibuy.smartdevice.ui;
import android.content.Context;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.ui.dialog.BuyDialog;
/**
* General (通用版) 銷售流程處理器
* 點選商品/購物車時彈出標準付款 Dialog (BuyDialog)
*/
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 onBuyRequested() {
new BuyDialog(context, handlerMain).show();
}
public void onBarcodeReceived(String rawData) {
// 通用版不處理掃碼
}
public void onPickupRequested() {
// 通用版不處理取貨碼
}
}

View File

@ -0,0 +1,264 @@
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

@ -0,0 +1,591 @@
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

@ -0,0 +1,194 @@
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

@ -343,7 +343,7 @@ public class HomeActivity extends AppCompatActivityAbstract {
protected void execute(HandlerMain handlerMain) {
for (int testIndex=0; testIndex < 30; testIndex++) {
if (testCount < 3) {
if (!MyApp.getInstance().isShowDialog()) {
if (!MyApp.getInstance().isMachineBusy()) {
getLogs().debug("RestartHost testCount:" + testCount);
testCount++;
} else {
@ -389,16 +389,16 @@ public class HomeActivity extends AppCompatActivityAbstract {
// 最多等待 10 分鐘120 * 5 確保機台有機會重啟
for (int i = 0; i < 120; i++) {
long idle = MyApp.getInstance().getIdleSeconds();
boolean isDialog = MyApp.getInstance().isShowDialog();
boolean isBusy = MyApp.getInstance().isMachineBusy();
if (idle >= 90 && !isDialog) {
getLogs().info("RestartAPP2閒置 " + idle + " 秒且無對話框,執行真重啟");
if (idle >= 90 && !isBusy) {
getLogs().info("RestartAPP2閒置 " + idle + " 秒且機台不忙碌,執行真重啟");
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
ReSetAPP2();
});
return;
}
getLogs().debug("RestartAPP2閒置 " + idle + " 秒,Dialog:" + isDialog + ",等待 5 秒後重新判斷 (" + (i+1) + "/120)");
getLogs().debug("RestartAPP2閒置 " + idle + " 秒,BusyReason:" + MyApp.getInstance().getBusyReason() + ",等待 5 秒後重新判斷 (" + (i+1) + "/120)");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
@ -427,7 +427,7 @@ public class HomeActivity extends AppCompatActivityAbstract {
protected void execute(HandlerMain handlerMain) {
for (int testIndex=0; testIndex < 30; testIndex++) {
if (testCount < 3) {
if (!MyApp.getInstance().isShowDialog()) {
if (!MyApp.getInstance().isMachineBusy()) {
getLogs().debug("RestartHost testCount:" + testCount);
testCount++;
} else {
@ -473,7 +473,7 @@ public class HomeActivity extends AppCompatActivityAbstract {
protected void execute(HandlerMain handlerMain) {
for (int testIndex=0; testIndex < 30; testIndex++) {
if (testCount < 3) {
if (!MyApp.getInstance().isShowDialog()) {
if (!MyApp.getInstance().isMachineBusy()) {
getLogs().debug("NfcCheckout testCount:" + testCount);
testCount++;
} else {
@ -556,7 +556,7 @@ public class HomeActivity extends AppCompatActivityAbstract {
Looper.prepare(); // 確保這個執行緒有 Looper
for (int testIndex=0; testIndex < 30; testIndex++) {
if (testCount < 3) {
if (!MyApp.getInstance().isShowDialog()) {
if (!MyApp.getInstance().isMachineBusy()) {
getLogs().debug("CMUHAutoRun testCount:" + testCount);
testCount++;
} else {

View File

@ -97,6 +97,8 @@ public class MyApp extends Application {
private Map<Integer, List<SlotStructure>> slotMap;
private List<MarketingPlanStructure> marketingPlanList;
private List<BuyStructure> buyList;
private String decryptedData;
private Map<String, List<MemoStructure>> memoMap;
private CashSetStructure cashSetStructure;
private AccessCodeStructure accessCodeStructure;
private FreeGiftStructure freeGiftStructure;
@ -277,6 +279,8 @@ public class MyApp extends Application {
initialSlotData();
this.marketingPlanList = new ArrayList<>();
this.buyList = new ArrayList<>();
this.memoMap = new HashMap<>();
initialMemoData();
PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(
UploadLogPeriodicWorker.class,
@ -667,6 +671,62 @@ public class MyApp extends Application {
return buyList;
}
public Logs getLogs() {
return logs;
}
public void setDecryptedData(String decryptedData) {
this.decryptedData = decryptedData;
}
public String getDecryptedData() {
return decryptedData;
}
public Map<String, List<MemoStructure>> getMemoMap() {
return memoMap;
}
private void initialMemoData() {
logs.debug("initialMemoData");
// 確保 memoMap 已初始化
if (getInstance().getMemoMap() == null) {
logs.info("memoMap初始化");
getInstance().memoMap = new HashMap<>();
}
// 開架構
if (!getInstance().getMemoMap().containsKey("CMUH")) {
getInstance().getMemoMap().put("CMUH", new ArrayList<>());
logs.info("CMUH memo db open");
}
// 查詢資料庫
MemoDao memoDao = new MemoDao(this);
try {
Map<String, List<MemoStructure>> memoMapNew = memoDao.getAll();
if (memoMapNew != null && memoMapNew.get("CMUH") != null && !memoMapNew.get("CMUH").isEmpty()) {
getInstance().getMemoMap().get("CMUH").addAll(memoMapNew.get("CMUH"));
}
} catch (Exception e) {
logs.info("📛 讀取 memo 資料庫失敗或無資料,跳過。");
} finally {
memoDao.close();
}
// 無資料就初始化一筆資料
if (getInstance().getMemoMap().get("CMUH").isEmpty()) {
logs.info("初始化CMUH Memo資料表");
String memoStr = "{\"ITEM\": [{\"SN\": \"00000000\", \"DRGNO\": \"0000\"}]}";
try {
getInstance().getMemoMap().get("CMUH").add(new MemoStructure("CMUH", new JSONObject(memoStr), new JSONObject()));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
public EsunpayStructure getEsunpay() {
return esunpay;
}

View File

@ -108,7 +108,7 @@ public class MemoDao {
}
// 獲取所有 Products
private Map<String, List<MemoStructure>> getAll() {
public Map<String, List<MemoStructure>> getAll() {
Map<String, List<MemoStructure>> memoMap = new HashMap<>();
Cursor cursor = database.query(

View File

@ -80,7 +80,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class FontendActivity extends AppCompatActivityAbstract {
public class FontendActivity extends AppCompatActivityAbstract implements SaleFlowCallback {
public enum Option {
TOAST(0),
BUY_DIALOG_OPEN(1),
@ -130,6 +130,18 @@ public class FontendActivity extends AppCompatActivityAbstract {
private Handler idleHandler = new Handler();
private Runnable idleRunnable;
private SaleFlowHandler saleFlowHandler;
@Override
public void onSaleFlowReset() {
getHandlerMain().start(getClass().getSimpleName(), Option.RESET_DATA.getOption(), "reset data");
}
@Override
public void onSaleFlowError(String message) {
getHandlerMain().start(getClass().getSimpleName(), Option.TOAST.getOption(), message);
}
// 遠端庫存更新監聽器輕量級只刷新 Adapter 不重建 Fragment
private final BroadcastReceiver slotUpdateReceiver = new BroadcastReceiver() {
@Override
@ -188,7 +200,11 @@ public class FontendActivity extends AppCompatActivityAbstract {
if (MyApp.getInstance().getBuyList().isEmpty()) {
start(getClass().getSimpleName(), getString(R.string.no_products_purchased));
} else {
new BuyDialog(context, this).show();
if (saleFlowHandler != null) {
saleFlowHandler.onBuyRequested();
} else {
new BuyDialog(context, this).show();
}
}
break;
case ADD_PRODUCT:
@ -391,6 +407,10 @@ public class FontendActivity extends AppCompatActivityAbstract {
binding = ActivityFontendBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 初始化銷售流程處理器
saleFlowHandler = new SaleFlowHandler(this, getHandlerMain(), this);
saleFlowHandler.onSetupUI();
httpAPI = new HttpAPI(getHandlerMain());
this.connectivityManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
@ -655,7 +675,11 @@ public class FontendActivity extends AppCompatActivityAbstract {
}
binding.floatButtonPickupCode.setOnClickListener(view -> {
new FontendPickupCodeDialog(getCtx(), getHandlerMain()).show();
if (saleFlowHandler != null) {
saleFlowHandler.onPickupRequested();
} else {
new FontendPickupCodeDialog(getCtx(), getHandlerMain()).show();
}
});
binding.floatButtonPickupCode.setVisibility(View.GONE);
@ -881,7 +905,7 @@ public class FontendActivity extends AppCompatActivityAbstract {
protected void execute(HandlerMain handlerMain) {
for (int testIndex = 0; testIndex < 30; testIndex++) {
if (testCount < 3) {
if (!MyApp.getInstance().isShowDialog()) {
if (!MyApp.getInstance().isMachineBusy()) {
getLogs().debug("NfcCheckout testCount:" + testCount);
testCount++;
} else {
@ -965,7 +989,7 @@ public class FontendActivity extends AppCompatActivityAbstract {
Looper.prepare(); // 確保這個執行緒有 Looper
for (int testIndex = 0; testIndex < 30; testIndex++) {
if (testCount < 3) {
if (!MyApp.getInstance().isShowDialog()) {
if (!MyApp.getInstance().isMachineBusy()) {
getLogs().debug("CMUHAutoRun testCount:" + testCount);
testCount++;
} else {

View File

@ -0,0 +1,14 @@
package com.unibuy.smartdevice.ui;
/**
* SaleFlowHandler -> FontendActivity 的回呼介面
* Handler 完成特定工作後透過此介面通知 Activity 更新 UI 或狀態
*/
public interface SaleFlowCallback {
/** 當銷售流程完整結束(已出貨或取消),需要重置前台狀態 */
void onSaleFlowReset();
/** 當流程中需要顯示錯誤訊息Toast / Dialog */
void onSaleFlowError(String message);
}

View File

@ -193,7 +193,7 @@ public class ElectricFragment extends FragmentAbstract {
throw new RuntimeException(e);
}
if (!MyApp.getInstance().isShowDialog()) {
if (!MyApp.getInstance().isMachineBusy()) {
try {
httpAPI.reportElectricInfo(electricInfoByJsonArray);
} catch (LogsParseException | LogsSettingEmptyException e) {

View File

@ -186,7 +186,7 @@ public class RecyclerElectricBuyListAdpter extends RecyclerView.Adapter<Recycler
public void onClick(View v) {
SlotStructure slotProduct = slotList.get(position);
if (slotProduct.isLock()) return;
if (MyApp.getInstance().isShowDialog()) return;
if (MyApp.getInstance().isMachineBusy()) return;
logs.info("marketingPlan:" + slotProduct.getProduct().getMarketingPlan());

View File

@ -156,7 +156,7 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter<RecyclerVmcBu
SlotStructure slotProduct = SlotList.get(position);
if (slotProduct.isLock())
return;
if (MyApp.getInstance().isShowDialog())
if (MyApp.getInstance().isMachineBusy())
return;
// 防呆正在檢查出貨口狀態時不允許再次點擊
if (isCheckingSlot)

View File

@ -38,6 +38,7 @@ public class VmcFragment extends FragmentAbstract {
ADD_PRODUCT_OPEN(3),
ADD_PRODUCT_CLOSE(4),
ADD_PRODUCT_FREE_GIFT(5),
DECRYPTION_ERROR(6),
;
private int option;
@ -129,6 +130,11 @@ public class VmcFragment extends FragmentAbstract {
sizeList.clear();
}
break;
case ADD_PRODUCT_FREE_GIFT:
break;
case DECRYPTION_ERROR:
super.execute(context, commandCode, "@掃碼失敗,請再掃描一次");
break;
}
}
};

View File

@ -39,7 +39,7 @@ public class UploadLogPeriodicWorker extends Worker {
public Result doWork() {
logs.debug("進入背景工作:"+getClass().getSimpleName()+" "+getId());
if (MyApp.getInstance().isShowDialog()) {
if (MyApp.getInstance().isMachineBusy()) {
logs.debug("系統使用中,延後處理備份事項");
return Result.retry();

View File

@ -390,7 +390,7 @@
<string name="there_is_something_at_the_pickup_port">There is something at the pickup port, please remove it first</string>
<string name="i_knew">knew</string>
<string name="input_vehicle">Input Vehicle</string>
<string name="reload_product">Reload Products</string>
<string name="reload_product_warning">This will download all products from the cloud and overwrite any unsaved local inventory changes. Are you sure you want to continue?</string>
<string name="sync_product">Reload Products</string>
<string name="sync_product_warning">This will download all products from the cloud and overwrite any unsaved local inventory changes. Are you sure you want to continue?</string>
<string name="downloading_product_data">Downloading product data...</string>
</resources>