- #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>
293 lines
7.9 KiB
Dart
293 lines
7.9 KiB
Dart
import 'package:networks_sdk/networks_sdk.dart';
|
||
|
||
import 'package:im_app/core/foundation/api_paths.dart';
|
||
|
||
// ── 发送红包 ──────────────────────────────────────────────────────────────────
|
||
|
||
/// 发送红包响应
|
||
class SendRpData {
|
||
final String rpId;
|
||
const SendRpData({required this.rpId});
|
||
|
||
factory SendRpData.fromJson(Map<String, dynamic> json) {
|
||
return SendRpData(
|
||
rpId: (json['rpID'] ?? json['rp_id'] ?? '').toString(),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 发送红包请求
|
||
///
|
||
/// 对应 Gitea issue #19 + #22
|
||
///
|
||
/// ## currencyType 规则(#19 bug fix)
|
||
///
|
||
/// iOS 硬编码 `"PEA"` → `code=150001` 错误(workspace 群使用 USDT)。
|
||
/// Flutter 修复:`currencyType` 由调用方传入,UseCase 层根据 workspaceId 动态决定:
|
||
/// - workspaceId > 0 → 从 `/workspace/workspace/get` 取 workspace.currency
|
||
/// - workspaceId == 0 → 默认 `"PEA"`
|
||
class SendRpRequest extends ApiRequestable<SendRpData> {
|
||
final String amount;
|
||
final String currencyType;
|
||
final int chatId;
|
||
final int chatType;
|
||
final String rpType;
|
||
final List<int> recipientIds;
|
||
final int rpNum;
|
||
final String remark;
|
||
final int msgSendTime;
|
||
|
||
const SendRpRequest({
|
||
required this.amount,
|
||
required this.currencyType,
|
||
required this.chatId,
|
||
required this.chatType,
|
||
required this.rpType,
|
||
required this.recipientIds,
|
||
required this.rpNum,
|
||
required this.remark,
|
||
required this.msgSendTime,
|
||
});
|
||
|
||
@override
|
||
String get path => ApiPaths.rpSend;
|
||
|
||
@override
|
||
HttpMethod get method => HttpMethod.post;
|
||
|
||
@override
|
||
Map<String, dynamic> get parameters => {
|
||
'amount': amount,
|
||
'currencyType': currencyType,
|
||
'chatID': chatId,
|
||
'chatType': chatType,
|
||
'rpType': rpType,
|
||
'recipientIDs': recipientIds,
|
||
'rpNum': rpNum,
|
||
'remark': remark,
|
||
'msgSendTime': msgSendTime,
|
||
};
|
||
|
||
@override
|
||
SendRpData? decodeResponse(dynamic response) {
|
||
final data = (response as dynamic).data;
|
||
if (data is! Map<String, dynamic>) return null;
|
||
return SendRpData.fromJson(data);
|
||
}
|
||
}
|
||
|
||
// ── 领取红包 ──────────────────────────────────────────────────────────────────
|
||
|
||
/// 领取红包响应
|
||
class ReceiveRpData {
|
||
final bool grabFlag;
|
||
final String amount;
|
||
final int rpStatus;
|
||
|
||
const ReceiveRpData({
|
||
required this.grabFlag,
|
||
required this.amount,
|
||
required this.rpStatus,
|
||
});
|
||
|
||
factory ReceiveRpData.fromJson(Map<String, dynamic> json) {
|
||
return ReceiveRpData(
|
||
grabFlag: json['grabFlag'] as bool? ?? false,
|
||
amount: (json['amount'] ?? '0').toString(),
|
||
rpStatus: json['rpStatus'] as int? ?? 0,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 领取红包请求
|
||
///
|
||
/// 对应 Gitea issue #21
|
||
///
|
||
/// ⚠️ 必须 JSON typed body(非 form 字符串),否则 server 返回 code=30007。
|
||
/// `supportMask: true` + `supportHideTail: true` 为必填标志位。
|
||
class ReceiveRpRequest extends ApiRequestable<ReceiveRpData> {
|
||
final String rpId;
|
||
final int chatId;
|
||
final String rpType;
|
||
final int sendRpMsgId;
|
||
|
||
const ReceiveRpRequest({
|
||
required this.rpId,
|
||
required this.chatId,
|
||
required this.rpType,
|
||
required this.sendRpMsgId,
|
||
});
|
||
|
||
@override
|
||
String get path => ApiPaths.rpReceive;
|
||
|
||
@override
|
||
HttpMethod get method => HttpMethod.post;
|
||
|
||
@override
|
||
Map<String, dynamic> get parameters => {
|
||
'rpID': rpId,
|
||
'chatID': chatId,
|
||
'rpType': rpType,
|
||
'sendRpMsgID': sendRpMsgId,
|
||
'supportMask': true,
|
||
'supportHideTail': true,
|
||
};
|
||
|
||
@override
|
||
ReceiveRpData? decodeResponse(dynamic response) {
|
||
final data = (response as dynamic).data;
|
||
if (data is! Map<String, dynamic>) return null;
|
||
return ReceiveRpData.fromJson(data);
|
||
}
|
||
}
|
||
|
||
// ── 游戏横幅 ──────────────────────────────────────────────────────────────────
|
||
|
||
/// 游戏横幅响应
|
||
class GameBannerData {
|
||
final String gameId;
|
||
final String gameName;
|
||
final String? appId;
|
||
final GameCurrentRound? currentRound;
|
||
final GameLastRound? lastRound;
|
||
|
||
const GameBannerData({
|
||
required this.gameId,
|
||
required this.gameName,
|
||
this.appId,
|
||
this.currentRound,
|
||
this.lastRound,
|
||
});
|
||
|
||
factory GameBannerData.fromJson(Map<String, dynamic> json) {
|
||
return GameBannerData(
|
||
gameId: json['gameId'] as String? ?? '',
|
||
gameName: json['gameName'] as String? ?? '',
|
||
appId: json['appid'] as String?,
|
||
currentRound: json['currentRound'] is Map<String, dynamic>
|
||
? GameCurrentRound.fromJson(json['currentRound'] as Map<String, dynamic>)
|
||
: null,
|
||
lastRound: json['lastCompletedRound'] is Map<String, dynamic>
|
||
? GameLastRound.fromJson(json['lastCompletedRound'] as Map<String, dynamic>)
|
||
: null,
|
||
);
|
||
}
|
||
}
|
||
|
||
class GameCurrentRound {
|
||
final String round;
|
||
final int? startTime;
|
||
final int? closureTime;
|
||
final int? drawTime;
|
||
final int? serverTime;
|
||
|
||
const GameCurrentRound({
|
||
required this.round,
|
||
this.startTime,
|
||
this.closureTime,
|
||
this.drawTime,
|
||
this.serverTime,
|
||
});
|
||
|
||
factory GameCurrentRound.fromJson(Map<String, dynamic> json) {
|
||
return GameCurrentRound(
|
||
round: json['round'] as String? ?? '',
|
||
startTime: json['startTime'] as int?,
|
||
closureTime: json['closureTime'] as int?,
|
||
drawTime: json['drawTime'] as int?,
|
||
serverTime: json['serverTime'] as int?,
|
||
);
|
||
}
|
||
}
|
||
|
||
class GameLastRound {
|
||
final String round;
|
||
final String result;
|
||
final String? simple;
|
||
|
||
const GameLastRound({
|
||
required this.round,
|
||
required this.result,
|
||
this.simple,
|
||
});
|
||
|
||
factory GameLastRound.fromJson(Map<String, dynamic> json) {
|
||
return GameLastRound(
|
||
round: json['round'] as String? ?? '',
|
||
result: json['result'] as String? ?? '',
|
||
simple: json['simple'] as String?,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 获取游戏横幅请求
|
||
class FetchBannerRequest extends ApiRequestable<GameBannerData> {
|
||
final String gameId;
|
||
|
||
const FetchBannerRequest({required this.gameId});
|
||
|
||
@override
|
||
String get path => ApiPaths.bannerGet;
|
||
|
||
@override
|
||
HttpMethod get method => HttpMethod.post;
|
||
|
||
@override
|
||
Map<String, dynamic> get parameters => {'game_id': gameId};
|
||
|
||
@override
|
||
GameBannerData? decodeResponse(dynamic response) {
|
||
final data = (response as dynamic).data;
|
||
if (data is! Map<String, dynamic>) return null;
|
||
return GameBannerData.fromJson(data);
|
||
}
|
||
}
|
||
|
||
// ── Workspace ─────────────────────────────────────────────────────────────────
|
||
|
||
/// Workspace 信息响应(用于获取 currency)
|
||
class WorkspaceData {
|
||
final int id;
|
||
final String currency;
|
||
final String name;
|
||
|
||
const WorkspaceData({
|
||
required this.id,
|
||
required this.currency,
|
||
required this.name,
|
||
});
|
||
|
||
factory WorkspaceData.fromJson(Map<String, dynamic> json) {
|
||
final ws = json['workspace'] as Map<String, dynamic>? ?? json;
|
||
return WorkspaceData(
|
||
id: ws['id'] as int? ?? 0,
|
||
currency: ws['currency'] as String? ?? 'PEA',
|
||
name: ws['name'] as String? ?? '',
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 获取 Workspace 信息请求
|
||
class GetWorkspaceRequest extends ApiRequestable<WorkspaceData> {
|
||
final int workspaceId;
|
||
|
||
const GetWorkspaceRequest({required this.workspaceId});
|
||
|
||
@override
|
||
String get path => ApiPaths.workspaceGet;
|
||
|
||
@override
|
||
HttpMethod get method => HttpMethod.get;
|
||
|
||
@override
|
||
Map<String, dynamic> get parameters => {'id': workspaceId.toString()};
|
||
|
||
@override
|
||
WorkspaceData? decodeResponse(dynamic response) {
|
||
final data = (response as dynamic).data;
|
||
if (data is! Map<String, dynamic>) return null;
|
||
return WorkspaceData.fromJson(data);
|
||
}
|
||
}
|