新增精灵图贴纸类型及内置资源 优化分类图标使用SVG替代emoji 实现分页预加载功能 修复API基础地址与客户端一致 新增健康生活、国学经典服务模块 扩展Feed频道至44种并整合互动统计 修正多处UI显示问题及逻辑错误
217 lines
5.8 KiB
Dart
217 lines
5.8 KiB
Dart
// ============================================================
|
||
// 闲言APP — 精灵图动画贴纸数据模型
|
||
// 创建时间: 2026-04-29
|
||
// 更新时间: 2026-04-29
|
||
// 作用: 定义精灵图贴纸和表情包的数据结构
|
||
// 上次更新: 初始创建
|
||
// ============================================================
|
||
|
||
/// 精灵图贴纸来源
|
||
enum SpritesheetSource {
|
||
builtin('builtin', '内置'),
|
||
local('local', '本地导入'),
|
||
remote('remote', '远程下载');
|
||
|
||
const SpritesheetSource(this.key, this.label);
|
||
final String key;
|
||
final String label;
|
||
}
|
||
|
||
/// 精灵图贴纸 — 单个动画贴纸
|
||
class SpritesheetSticker {
|
||
const SpritesheetSticker({
|
||
required this.id,
|
||
required this.name,
|
||
required this.emoji,
|
||
required this.source,
|
||
required this.imageUri,
|
||
required this.frameWidth,
|
||
required this.frameHeight,
|
||
required this.totalFrames,
|
||
this.fps = 12,
|
||
this.loop = true,
|
||
this.packId,
|
||
this.thumbnailUri,
|
||
this.columns,
|
||
this.rows,
|
||
});
|
||
|
||
final String id;
|
||
final String name;
|
||
final String emoji;
|
||
final SpritesheetSource source;
|
||
final String imageUri;
|
||
final int frameWidth;
|
||
final int frameHeight;
|
||
final int totalFrames;
|
||
final int fps;
|
||
final bool loop;
|
||
final String? packId;
|
||
final String? thumbnailUri;
|
||
final int? columns;
|
||
final int? rows;
|
||
|
||
/// 推算列数:优先用 columns,否则假设单行排列
|
||
int get effectiveColumns => columns ?? totalFrames;
|
||
|
||
/// 推算行数:优先用 rows,否则为1
|
||
int get effectiveRows => rows ?? 1;
|
||
|
||
factory SpritesheetSticker.fromJson(Map<String, dynamic> json) {
|
||
return SpritesheetSticker(
|
||
id: json['id'] as String,
|
||
name: json['name'] as String,
|
||
emoji: json['emoji'] as String? ?? '🎭',
|
||
source: SpritesheetSource.values.firstWhere(
|
||
(s) => s.key == json['source'],
|
||
orElse: () => SpritesheetSource.builtin,
|
||
),
|
||
imageUri: json['imageUri'] as String,
|
||
frameWidth: json['frameWidth'] as int,
|
||
frameHeight: json['frameHeight'] as int,
|
||
totalFrames: json['totalFrames'] as int,
|
||
fps: json['fps'] as int? ?? 12,
|
||
loop: json['loop'] as bool? ?? true,
|
||
packId: json['packId'] as String?,
|
||
thumbnailUri: json['thumbnailUri'] as String?,
|
||
columns: json['columns'] as int?,
|
||
rows: json['rows'] as int?,
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'id': id,
|
||
'name': name,
|
||
'emoji': emoji,
|
||
'source': source.key,
|
||
'imageUri': imageUri,
|
||
'frameWidth': frameWidth,
|
||
'frameHeight': frameHeight,
|
||
'totalFrames': totalFrames,
|
||
'fps': fps,
|
||
'loop': loop,
|
||
'packId': packId,
|
||
'thumbnailUri': thumbnailUri,
|
||
'columns': columns,
|
||
'rows': rows,
|
||
};
|
||
|
||
SpritesheetSticker copyWith({
|
||
String? id,
|
||
String? name,
|
||
String? emoji,
|
||
SpritesheetSource? source,
|
||
String? imageUri,
|
||
int? frameWidth,
|
||
int? frameHeight,
|
||
int? totalFrames,
|
||
int? fps,
|
||
bool? loop,
|
||
String? packId,
|
||
String? thumbnailUri,
|
||
int? columns,
|
||
int? rows,
|
||
}) {
|
||
return SpritesheetSticker(
|
||
id: id ?? this.id,
|
||
name: name ?? this.name,
|
||
emoji: emoji ?? this.emoji,
|
||
source: source ?? this.source,
|
||
imageUri: imageUri ?? this.imageUri,
|
||
frameWidth: frameWidth ?? this.frameWidth,
|
||
frameHeight: frameHeight ?? this.frameHeight,
|
||
totalFrames: totalFrames ?? this.totalFrames,
|
||
fps: fps ?? this.fps,
|
||
loop: loop ?? this.loop,
|
||
packId: packId ?? this.packId,
|
||
thumbnailUri: thumbnailUri ?? this.thumbnailUri,
|
||
columns: columns ?? this.columns,
|
||
rows: rows ?? this.rows,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 表情包 — 一组精灵图贴纸
|
||
class StickerPack {
|
||
const StickerPack({
|
||
required this.id,
|
||
required this.name,
|
||
required this.icon,
|
||
required this.source,
|
||
required this.stickers,
|
||
this.coverUri,
|
||
this.downloadUrl,
|
||
this.fileSize,
|
||
this.isDownloaded = false,
|
||
});
|
||
|
||
final String id;
|
||
final String name;
|
||
final String icon;
|
||
final SpritesheetSource source;
|
||
final List<SpritesheetSticker> stickers;
|
||
final String? coverUri;
|
||
final String? downloadUrl;
|
||
final int? fileSize;
|
||
final bool isDownloaded;
|
||
|
||
factory StickerPack.fromJson(Map<String, dynamic> json) {
|
||
return StickerPack(
|
||
id: json['id'] as String,
|
||
name: json['name'] as String,
|
||
icon: json['icon'] as String? ?? '🎭',
|
||
source: SpritesheetSource.values.firstWhere(
|
||
(s) => s.key == json['source'],
|
||
orElse: () => SpritesheetSource.builtin,
|
||
),
|
||
stickers:
|
||
(json['stickers'] as List?)
|
||
?.map(
|
||
(s) => SpritesheetSticker.fromJson(s as Map<String, dynamic>),
|
||
)
|
||
.toList() ??
|
||
[],
|
||
coverUri: json['coverUri'] as String?,
|
||
downloadUrl: json['downloadUrl'] as String?,
|
||
fileSize: json['fileSize'] as int?,
|
||
isDownloaded: json['isDownloaded'] as bool? ?? false,
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'id': id,
|
||
'name': name,
|
||
'icon': icon,
|
||
'source': source.key,
|
||
'stickers': stickers.map((s) => s.toJson()).toList(),
|
||
'coverUri': coverUri,
|
||
'downloadUrl': downloadUrl,
|
||
'fileSize': fileSize,
|
||
'isDownloaded': isDownloaded,
|
||
};
|
||
|
||
StickerPack copyWith({
|
||
String? id,
|
||
String? name,
|
||
String? icon,
|
||
SpritesheetSource? source,
|
||
List<SpritesheetSticker>? stickers,
|
||
String? coverUri,
|
||
String? downloadUrl,
|
||
int? fileSize,
|
||
bool? isDownloaded,
|
||
}) {
|
||
return StickerPack(
|
||
id: id ?? this.id,
|
||
name: name ?? this.name,
|
||
icon: icon ?? this.icon,
|
||
source: source ?? this.source,
|
||
stickers: stickers ?? this.stickers,
|
||
coverUri: coverUri ?? this.coverUri,
|
||
downloadUrl: downloadUrl ?? this.downloadUrl,
|
||
fileSize: fileSize ?? this.fileSize,
|
||
isDownloaded: isDownloaded ?? this.isDownloaded,
|
||
);
|
||
}
|
||
}
|