UI: 更新購物車與取貨碼懸浮按鈕樣式及動態角標

- 將購物車與取貨碼改為統一的帶圓角 CardView 容器設計,使用 surface 背景與黑色圖示文字。
- 修正陰影裁切問題,移除外層容器的 clipChildren 限制。
- 購物車新增右上角紅色 Badge 顯示當前選取的商品總數,並與 CartViewModel 動態綁定。
- 調整懸浮按鈕位置,使其與底部機台資訊列保持 24dp 的合適間距。
- 新增 Google Material Icons: package_24 與 shopping_cart_24。
- 同步更新相關的 Agent 發版規則文件,移除廢棄的多維度 flavor 定義。
This commit is contained in:
TaiwanStar Developer 2026-06-11 15:18:38 +08:00
parent 2683b850a2
commit ee732e03a3
6 changed files with 177 additions and 46 deletions

View File

@ -28,8 +28,8 @@ trigger: always_on
依據公司標準規範,實體機台發佈之 APK 與版本名稱需嚴格符合以下欄位對照:
```
來源 _ 安卓號 _ 主板號 _ 機器系列 _ 專案名稱 _ 螢幕大小 _ 次版號 _ 修補版號 _ 環境
S _ 12 _ XY _ U _ Chengwai _ 10 _ 08 _ 8 _ R
來源 _ 安卓號 _ 主板號 _ 機器系列 _ 螢幕大小 _ 次版號 _ 修補版號 _ 環境
S _ 12 _ XY _ U _ 10 _ 08 _ 8 _ R
```
### 欄位解析:
@ -37,7 +37,6 @@ S _ 12 _ XY _ U _ Chengwai _ 10 _ 08 _ 8 _ R
* **`12` (安卓號)**Android 系統主版本號。
* **`XY` (主板號)**:主板型號規格。
* **`U` (機器系列 / 機型規格)**:例如代表 U 系列機型。
* **`Chengwai` (專案名稱)**:客戶/銷售場景專案代碼(如 `General`, `Chengwai`, `Cmuh`)。
* **`10` (螢幕大小)**:對應 Gradle 中的 `verMajor`
* **`08` (次版號)**:對應 Gradle 中的 `verMinor`(格式化為雙位數)。
* **`8` (修補版號)**:對應 Gradle 中的 `verPatch`
@ -55,38 +54,32 @@ S _ 12 _ XY _ U _ Chengwai _ 10 _ 08 _ 8 _ R
---
## 4. 雙維度產品風味 (Product Flavors) 與 APK 命名規範
## 4. 單維度硬體規格 (Hardware Flavor) 與 APK 命名規範
本專案採用**雙維度 Flavor** 架構,以完美分離「客戶銷售場景」與「底層硬體規格」,方便後續快速擴充。
本專案原本採用雙維度,但目前客戶專案(購買流程)已收斂為執行期 `shopping_mode`(雲端 B014 下發)動態決定,因此不再使用 flavor 分家。
目前僅保留**單一維度 `hardware`**,專門處理底層硬體規格差異。
### 4.1 維度定義 (flavorDimensions)
本專案於 `app/build.gradle` 中定義了兩個 Flavor 維度
### 4.1 硬體維度定義 (flavorDimensions)
本專案於 `app/build.gradle` 中定義了唯一的 Flavor 維度 `hardware`
1. **`project` 維度 (客戶/銷售場景區隔)**
* **`General`** (通用主幹):預設顯示付款方式選單(彈出 `BuyDialog`)。未來新客戶最常用此通用主幹。
* **`Chengwai`** (晟崴客製):直接跳出員工卡刷卡畫面(彈出 `MemberCardPickupDialog`),不顯示付款選單。
* **`Cmuh`** (中國醫客製):支援 QR Code 掃碼、處方籤醫令離線 AES 解密與解析領藥流程。
2. **`hardware` 維度 (硬體與系統規格區隔)**
* **`S12XYU`**:代表 `S` (來源) + `12` (安卓號) + `XY` (主板號) + `U` (機器系列/機型規格)。
* **`S9XYU`**:代表中國醫現場使用的 Android 9 / 21.5 吋硬體規格;除非明確指定中國醫 10 吋或 `S12XYU`,中國醫 (`Cmuh`) 相關修正與驗證預設必須使用此硬體 flavor。
* **`S12XYU`** (10 吋預設硬體):代表 `S` (來源) + `12` (安卓號) + `XY` (主板號) + `U` (機器系列/機型規格)。
* **`S9XYU`** (21.5 吋硬體):代表中國醫現場使用的 Android 9 / 21.5 吋硬體規格。
### 4.2 Flavor 驗證與編譯指令選擇
針對特定客戶或現場問題修改後,必須編譯對應的 Gradle variant不可只用通用 `assembleDebug` 或錯誤硬體 flavor 代替。
修改後,必須編譯對應硬體規格的 Gradle variant不可只用通用 `assembleDebug`
* **中國醫現場版 (`Cmuh`) 驗證指令**`./gradlew :app:assembleCmuhS9XYUDebug --no-daemon`
* **晟崴版 (`Chengwai`) 預設驗證指令**`./gradlew :app:assembleChengwaiS12XYUDebug --no-daemon`
* **通用版 (`General`) 預設驗證指令**`./gradlew :app:assembleGeneralS12XYUDebug --no-daemon`
* **10 吋一般機台版 (含晟崴、通用等)**`./gradlew :app:assembleS12XYUDebug --no-daemon`
* **21.5 吋中國醫等大螢幕機台版**`./gradlew :app:assembleS9XYUDebug --no-daemon`
### 4.3 實體機台 APK 命名鐵律
為了符合實體機台 OTA 平台發佈規範,打包輸出的 APK 檔名必須嚴格連動雙維度,並遵循以下格式:
`app-[格式化硬體規格]_[專案名稱]-[版本號]-[BuildType].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-S_12_XY_U-10_08_8_R-release.apk`
* `app-S_9_XY_U-21_08_8_R-release.apk`
* **注意**:此檔名轉換規則已完全由 `app/build.gradle` 頂部 Gradle 變數與 `applicationVariants.all` 自動連動生成,**嚴禁手動修改輸出檔名**。
---

View File

@ -24,7 +24,8 @@
- 禁止直接手改成品 `versionCode`/`versionName` 值。
- 版號管理透過 `verMajor`、`verMinor`、`verPatch` 變數。
- OTA 發版時,`versionCode` 必須嚴格遞增。
- 中國醫現場版 (`Cmuh`) 修正預設以 `:app:assembleCmuhS9XYUDebug` 驗證。
- 21.5 吋大螢幕現場版(如中國醫)修正預設以 `:app:assembleS9XYUDebug` 驗證。
- 10 吋一般現場版(如晟崴、通用)修正預設以 `:app:assembleS12XYUDebug` 驗證。
## When Rules Conflict

View File

@ -737,6 +737,22 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
binding.floatButtonShoppingCart.setVisibility(View.GONE);
}
// 觀察購物車內容以更新數量 Badge
com.unibuy.v2.UnibuyHelper.INSTANCE.getCartViewModel().getCartItems().observe(this, cartItems -> {
int totalCount = 0;
if (cartItems != null) {
for (com.unibuy.smartdevice.structure.SlotStructure item : cartItems) {
totalCount += item.cartSelectedCount;
}
}
if (totalCount > 0) {
binding.textCartBadge.setText(String.valueOf(totalCount));
binding.textCartBadge.setVisibility(View.VISIBLE);
} else {
binding.textCartBadge.setVisibility(View.GONE);
}
});
startHeaderReportScheduler();
// binding.btnswitchlanTW.setOnClickListener(v -> switchLanguage("tw"));

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@android:color/white">
<path
android:fillColor="@android:color/white"
android:pathData="M20,2H4C3,2 2,2.9 2,4v3.01C2,7.73 2.43,8.35 3,8.7V20c0,1.1 1.1,2 2,2h14c0.9,0 2,-0.9 2,-2V8.7c0.57,-0.35 1,-0.97 1,-1.69V4C22,2.9 21,2 20,2zM15,14H9v-2h6V14zM20,7H4V4h16V7z"/>
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@android:color/white">
<path
android:fillColor="@android:color/white"
android:pathData="M6,2 L3,2 L3,4 L5,4 L8.6,11.6 L7.3,14 C7.1,14.3 7,14.7 7,15 C7,16.1 7.9,17 9,17 L21,17 L21,15 L9.4,15 C9.3,15 9.2,14.9 9.2,14.8 L9.2,14.7 L10.1,13 L18,13 C18.8,13 19.5,12.6 19.8,11.9 L23,6.3 L23,6 C23,5.4 22.6,5 22,5 L7.4,5 L6.5,3 Z" />
<path
android:fillColor="@android:color/white"
android:pathData="M9,18 C7.9,18 7,18.9 7,20 C7,21.1 7.9,22 9,22 C10.1,22 11,21.1 11,20 C11,18.9 10.1,18 9,18 Z" />
<path
android:fillColor="@android:color/white"
android:pathData="M19,18 C17.9,18 17,18.9 17,20 C17,21.1 17.9,22 19,22 C20.1,22 21,21.1 21,20 C21,18.9 20.1,18 19,18 Z" />
</vector>

View File

@ -231,37 +231,132 @@
</LinearLayout>
<TextView
<LinearLayout
android:id="@+id/floatButtonPickupCode"
android:layout_width="50dp"
android:layout_height="150dp"
android:text="取\n貨\n碼"
android:textSize="28sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:layout_marginEnd="24dp"
android:layout_marginBottom="80dp"
android:backgroundTint="@color/gray"
android:textColor="@color/black"
android:alpha="0.8"
android:layout_marginBottom="24dp"
android:visibility="gone"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintBottom_toTopOf="@+id/floatButtonShoppingCart"
app:layout_constraintEnd_toEndOf="@+id/bottom_navigation"
android:textStyle="bold"/>
app:layout_constraintEnd_toEndOf="@+id/bottom_navigation">
<com.google.android.material.floatingactionbutton.FloatingActionButton
<androidx.cardview.widget.CardView
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_margin="12dp"
app:cardCornerRadius="16dp"
app:cardBackgroundColor="@color/cardSurface"
app:cardElevation="6dp"
android:layout_gravity="center"
android:foreground="?android:attr/selectableItemBackground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:paddingTop="12dp"
android:paddingBottom="8dp"
android:paddingHorizontal="8dp">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/package_24"
app:tint="@color/black" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="取貨碼"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
<LinearLayout
android:id="@+id/floatButtonShoppingCart"
android:layout_width="58dp"
android:layout_height="51dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:layout_marginEnd="24dp"
android:layout_marginBottom="80dp"
android:contentDescription="TODO"
android:layout_marginBottom="24dp"
android:visibility="gone"
app:backgroundTint="@color/gray"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="@+id/fragment_container"
app:layout_constraintEnd_toEndOf="@+id/bottom_navigation"
app:maxImageSize="60dp"
app:srcCompat="@drawable/baseline_add_shopping_cart_24" />
app:layout_constraintEnd_toEndOf="@+id/bottom_navigation">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false">
<androidx.cardview.widget.CardView
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_margin="12dp"
app:cardCornerRadius="16dp"
app:cardBackgroundColor="@color/cardSurface"
app:cardElevation="6dp"
android:layout_gravity="center"
android:foreground="?android:attr/selectableItemBackground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:paddingTop="12dp"
android:paddingBottom="8dp"
android:paddingHorizontal="8dp">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/shopping_cart_24"
app:tint="@color/black" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/shopping_cart_title"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/textCartBadge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="34dp"
android:minHeight="34dp"
android:paddingHorizontal="6dp"
android:layout_gravity="top|end"
android:background="@drawable/rounded_background_red"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:elevation="10dp"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
<Button