- #19 fix: SendRedEnvelopeUseCase 动态取 currencyType(workspaceId>0 取 workspace.currency,修复 iOS 硬编码 PEA → 150001 错误) - #20: RedEnvelopeBubble typ=8,四态(橙色领取/已领/过期/抢完)+ 领取按钮 - #21: ReceiveRedEnvelopeUseCase POST /app/api/wallet/rp/receive, typed JSON body(避免 code=30007),SnackBar 反馈 - #22: SendRedEnvelopeSheet BottomSheet,STANDARD_RP + LUCKY_RP, 发送成功后构建 typ=8 content JSON 回调给 ChatPage - #23: BannerViewModel Notifier,Group.topic 双格式解析(JSON object/string), FetchBannerUseCase + Timer 倒计时 + applyNewRound WS 接口 - #24: BannerView 游戏横幅条(状态/倒计时/上期结果), MiniAppFloatButton 悬浮按钮(hasGame 显示/隐藏,onTap TODO #25) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
174 lines
4.9 KiB
Markdown
174 lines
4.9 KiB
Markdown
# 红包与游戏横幅 — 架构文档
|
||
|
||
> 对应 Gitea issues #19–#24
|
||
> 参考实现:`im-client-ios-swift-demo` RedEnvelopeSendView + BannerViewModel + PlatformBannerViewModel
|
||
> Bug 参考:bug 截图文件夹(currencyType=PEA 错误 + [红包] 文本未渲染)
|
||
|
||
---
|
||
|
||
## 1. 功能范围
|
||
|
||
| Issue | 功能 | 状态 |
|
||
|-------|------|------|
|
||
| #19 | currencyType 动态化(修复 PEA 硬编码) | ✅ 已实现 |
|
||
| #20 | 红包消息气泡(typ=8,三态 UI) | ✅ 已实现 |
|
||
| #21 | 领取红包(/app/api/wallet/rp/receive) | ✅ 已实现 |
|
||
| #22 | 发送红包 UI(STANDARD_RP + LUCKY_RP) | ✅ 已实现 |
|
||
| #23 | BannerViewModel — 游戏横幅 + WS NewRound | ✅ 已实现 |
|
||
| #24 | 游戏悬浮按钮(MiniAppFloatButton) | ✅ 已实现 |
|
||
|
||
---
|
||
|
||
## 2. 目录结构
|
||
|
||
```
|
||
data/remote/
|
||
├── red_envelope_request.dart # 发送/领取 API Request
|
||
└── banner_request.dart # 游戏横幅 API Request
|
||
|
||
features/chat/
|
||
├── di/
|
||
│ └── red_envelope_provider.dart # 红包 DI 装配
|
||
├── presentation/
|
||
│ └── banner_view_model.dart # 游戏横幅 ViewModel
|
||
├── usecases/
|
||
│ ├── send_red_envelope_usecase.dart # 发送红包
|
||
│ ├── receive_red_envelope_usecase.dart # 领取红包
|
||
│ └── fetch_banner_usecase.dart # 拉取游戏横幅
|
||
└── view/widgets/
|
||
├── red_envelope_bubble.dart # 红包气泡(typ=8)
|
||
├── send_red_envelope_sheet.dart # 发送 BottomSheet
|
||
├── banner_view.dart # 游戏横幅条
|
||
└── miniapp_float_button.dart # 游戏悬浮按钮
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 关键数据格式
|
||
|
||
### 3.1 红包消息 content(typ=8)
|
||
|
||
```json
|
||
{
|
||
"id": "rp_xxx",
|
||
"rp_type": "STANDARD_RP",
|
||
"remark": "恭喜发财",
|
||
"total_amount": "10.00",
|
||
"total_num": 5,
|
||
"rp_status": 0
|
||
}
|
||
```
|
||
|
||
**rp_status 含义:**
|
||
| 值 | 含义 | UI |
|
||
|----|------|----|
|
||
| 0/1 | 未领取 | 橙色 + 领取按钮 |
|
||
| 2 | 已领取 | 灰色 "已领取" |
|
||
| 3 | 已过期 | 灰色 "红包已过期" |
|
||
| 4 | 已抢完 | 灰色 "手慢了" |
|
||
| 6 | 等待开奖(NN_RP) | 橙色 + 等待 |
|
||
|
||
### 3.2 发送红包 API
|
||
|
||
**POST /payment/rp/send**
|
||
|
||
```json
|
||
{
|
||
"amount": "10.00",
|
||
"currencyType": "PEA",
|
||
"chatID": 12345,
|
||
"chatType": 2,
|
||
"rpType": "STANDARD_RP",
|
||
"recipientIDs": [],
|
||
"rpNum": 5,
|
||
"remark": "恭喜发财",
|
||
"msgSendTime": 1234567890000000
|
||
}
|
||
```
|
||
|
||
**⚠️ currencyType 规则:**
|
||
- `workspaceId > 0` → 从 `/workspace/workspace/get` 取 `workspace.currency`(如 `"USDT"`)
|
||
- `workspaceId == 0` → `"PEA"`(默认)
|
||
|
||
### 3.3 领取红包 API
|
||
|
||
**POST /app/api/wallet/rp/receive**
|
||
|
||
```json
|
||
{
|
||
"rpID": "rp_xxx",
|
||
"chatID": 12345,
|
||
"rpType": "STANDARD_RP",
|
||
"sendRpMsgID": 99999,
|
||
"supportMask": true,
|
||
"supportHideTail": true
|
||
}
|
||
```
|
||
|
||
**⚠️ 必须 JSON typed body(非 form 字符串),否则 code=30007**
|
||
|
||
---
|
||
|
||
## 4. 数据流
|
||
|
||
### 4.1 发送红包
|
||
|
||
```
|
||
ChatPage → SendRedEnvelopeSheet
|
||
└─ SendRedEnvelopeViewModel.send(params)
|
||
├─ currencyType = workspaceId > 0 ? workspace.currency : "PEA"
|
||
├─ SendRedEnvelopeUseCase.execute() → POST /payment/rp/send → rpID
|
||
└─ 构建 rawContent JSON → 通过 ChatViewModel.sendMessage(typ=8, content)
|
||
```
|
||
|
||
### 4.2 领取红包
|
||
|
||
```
|
||
RedEnvelopeBubble.onTap
|
||
└─ ReceiveRedEnvelopeUseCase.execute(rpID, chatID, rpType, messageId)
|
||
└─ POST /app/api/wallet/rp/receive
|
||
→ grabFlag=true → SnackBar 显示金额
|
||
→ grabFlag=false → SnackBar 显示原因
|
||
```
|
||
|
||
### 4.3 游戏横幅
|
||
|
||
```
|
||
ChatPage (群聊)
|
||
└─ BannerViewModel(chatId).build()
|
||
├─ 解析 Group.topic JSON → gameId
|
||
├─ FetchBannerUseCase(gameId) → POST /lucky/banner/get
|
||
├─ Timer 每秒 tick → countdown
|
||
└─ SocketManager 消息流 → 过滤 miniapp.NewRound
|
||
→ applyGameInfo(bean) → 刷新横幅
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Provider 设计
|
||
|
||
```
|
||
sendRedEnvelopeUseCaseProvider → networkSdkApiProvider
|
||
receiveRedEnvelopeUseCaseProvider → networkSdkApiProvider
|
||
fetchBannerUseCaseProvider → networkSdkApiProvider
|
||
bannerViewModelProvider.family(chatId)
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Bug 修复说明
|
||
|
||
| Bug | 原因 | 修复 |
|
||
|-----|------|------|
|
||
| code=150001 | iOS 硬编码 `currencyType=PEA`,workspace 用 USDT | Flutter 动态取 workspace.currency |
|
||
| `[红包]` 文本 | 旧消息 typ=1 content="[红包]",未路由到红包气泡 | `typ=8` → RedEnvelopeBubble;typ=1 content=="[红包]" fallback 检测 |
|
||
|
||
---
|
||
|
||
## 7. 待完成
|
||
|
||
- **地雷红包 / 牛牛红包**:MINE_RP + NN_RP 发送 UI(需 rp-config/get)
|
||
- **Mini-app WebView**:MiniAppFloatButton 点击后打开小程序(#25)
|
||
- **领取详情页**:抢红包排行榜(iOS RedEnvelopeDetailSheet)
|
||
- **Game settle polling**:drawTime 后每 3s 轮询直到新一期
|