288 lines
8.9 KiB
Dart
288 lines
8.9 KiB
Dart
/// listing 为数字时的英文文案(兼容旧接口):1 队列中 | 2 处理中 | …
|
||
String _galleryListingLabelEnglish(int listing) {
|
||
switch (listing) {
|
||
case 1:
|
||
return 'Queued';
|
||
case 2:
|
||
return 'Processing';
|
||
case 3:
|
||
return 'Completed';
|
||
case 4:
|
||
return 'Timed out';
|
||
case 5:
|
||
return 'Error';
|
||
case 6:
|
||
return 'Aborted';
|
||
case 0:
|
||
return 'Pending';
|
||
default:
|
||
return 'Unknown';
|
||
}
|
||
}
|
||
|
||
/// 接口 `listing`:字符串(如 finished)原样展示;纯数字字符串或 int 仍走英文映射
|
||
String listingDisplayFromApi(dynamic raw) {
|
||
if (raw == null) return '';
|
||
if (raw is int) return _galleryListingLabelEnglish(raw);
|
||
if (raw is num) return _galleryListingLabelEnglish(raw.toInt());
|
||
final s = raw.toString().trim();
|
||
if (s.isEmpty) return '';
|
||
final asInt = int.tryParse(s);
|
||
if (asInt != null && s == asInt.toString()) {
|
||
return _galleryListingLabelEnglish(asInt);
|
||
}
|
||
return s;
|
||
}
|
||
|
||
bool galleryMediaHasRemoteUrl(GalleryMediaItem m) {
|
||
bool http(String? x) {
|
||
if (x == null) return false;
|
||
final t = x.trim();
|
||
return t.startsWith('http://') || t.startsWith('https://');
|
||
}
|
||
|
||
return http(m.imageUrl) || http(m.videoUrl);
|
||
}
|
||
|
||
/// 生成中:pending / queued / processing;兼容数字 0·1·2
|
||
bool galleryListingIsInProgress(dynamic raw, String display) {
|
||
final d = display.trim().toLowerCase();
|
||
if (d == 'pending' ||
|
||
d == 'queued' ||
|
||
d == 'processing' ||
|
||
d == 'in progress' ||
|
||
d == 'running') {
|
||
return true;
|
||
}
|
||
if (raw != null) {
|
||
if (raw is int && (raw == 0 || raw == 1 || raw == 2)) return true;
|
||
if (raw is num) {
|
||
final v = raw.toInt();
|
||
if (v == 0 || v == 1 || v == 2) return true;
|
||
}
|
||
final s = raw.toString().trim().toLowerCase();
|
||
if (s == 'pending' ||
|
||
s == 'queued' ||
|
||
s == 'processing' ||
|
||
s == 'in_progress' ||
|
||
s == 'in progress' ||
|
||
s == 'running') {
|
||
return true;
|
||
}
|
||
final n = int.tryParse(s);
|
||
if (n != null && (n == 0 || n == 1 || n == 2)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// 可去结果页:finished / completed;兼容数字 3
|
||
bool galleryListingIsFinishedSuccess(dynamic raw, String display) {
|
||
final d = display.trim().toLowerCase();
|
||
if (d == 'finished' ||
|
||
d == 'completed' ||
|
||
d == 'complete' ||
|
||
d == 'success' ||
|
||
d == 'done') {
|
||
return true;
|
||
}
|
||
if (raw != null) {
|
||
if (raw is int && raw == 3) return true;
|
||
if (raw is num && raw.toInt() == 3) return true;
|
||
final s = raw.toString().trim().toLowerCase();
|
||
if (s == 'finished' ||
|
||
s == 'completed' ||
|
||
s == 'complete' ||
|
||
s == 'success' ||
|
||
s == 'done') {
|
||
return true;
|
||
}
|
||
if (int.tryParse(s) == 3) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// 不可跳转结果/进度时的英文提示
|
||
String galleryListingBlockedHint(dynamic raw, String display) {
|
||
int? code;
|
||
if (raw is int) {
|
||
code = raw;
|
||
} else if (raw is num) {
|
||
code = raw.toInt();
|
||
} else if (raw != null) {
|
||
final s = raw.toString().trim().toLowerCase();
|
||
if (s == 'timeout' || s == 'timed out' || s == 'timed_out') {
|
||
code = 4;
|
||
} else if (s == 'error' || s == 'failed' || s == 'failure') {
|
||
code = 5;
|
||
} else if (s == 'aborted' || s == 'cancelled' || s == 'canceled') {
|
||
code = 6;
|
||
} else {
|
||
code = int.tryParse(s);
|
||
}
|
||
}
|
||
switch (code) {
|
||
case 4:
|
||
return 'This task has timed out.';
|
||
case 5:
|
||
return 'This task failed. Please try again.';
|
||
case 6:
|
||
return 'This task was cancelled.';
|
||
default:
|
||
final low = display.trim().toLowerCase();
|
||
if (low.contains('timeout') || low.contains('timed out')) {
|
||
return 'This task has timed out.';
|
||
}
|
||
if (low.contains('error') || low.contains('fail')) {
|
||
return 'This task failed. Please try again.';
|
||
}
|
||
if (low.contains('abort') || low.contains('cancel')) {
|
||
return 'This task was cancelled.';
|
||
}
|
||
return 'This item is not available yet.';
|
||
}
|
||
}
|
||
|
||
/// 媒体项:reconfigure=imgUrl,类型由 reconnect(imgType) 决定:0=视频,1=图片,其他当图片
|
||
class GalleryMediaItem {
|
||
GalleryMediaItem({
|
||
this.imageUrl,
|
||
this.videoUrl,
|
||
this.taskId,
|
||
this.createTime = 0,
|
||
this.createTimeText,
|
||
this.listingDisplay = '',
|
||
this.listingRaw,
|
||
}) : assert(
|
||
(imageUrl != null && imageUrl.isNotEmpty) ||
|
||
(videoUrl != null && videoUrl.isNotEmpty) ||
|
||
(taskId != null && taskId > 0),
|
||
);
|
||
|
||
final String? imageUrl;
|
||
final String? videoUrl;
|
||
/// 与列表项 `tree` 一致,用于匹配本地上传封面缓存
|
||
final int? taskId;
|
||
/// 任务创建时间戳(discover,秒或毫秒)
|
||
final int createTime;
|
||
/// 服务端格式化创建时间(uncover),有则优先展示
|
||
final String? createTimeText;
|
||
/// 任务状态展示文案(接口 listing:字符串直出或数字映射为英文)
|
||
final String listingDisplay;
|
||
/// 接口原始 `listing`(字符串/数字),用于与 [listingDisplay] 一起做跳转判断
|
||
final dynamic listingRaw;
|
||
|
||
/// reconnect==0 为视频,1 或其他为图片
|
||
bool get isVideo =>
|
||
videoUrl != null && (imageUrl == null || imageUrl!.isEmpty);
|
||
}
|
||
|
||
/// 我的任务项(V2 字段映射)
|
||
class GalleryTaskItem {
|
||
const GalleryTaskItem({
|
||
required this.taskId,
|
||
required this.state,
|
||
required this.taskType,
|
||
required this.createTime,
|
||
required this.mediaItems,
|
||
});
|
||
|
||
final int taskId;
|
||
final String state;
|
||
final int taskType;
|
||
final int createTime;
|
||
final List<GalleryMediaItem> mediaItems;
|
||
|
||
factory GalleryTaskItem.fromJson(Map<String, dynamic> json) {
|
||
final treeRaw = json['tree'] as num?;
|
||
final treeId = treeRaw?.toInt() ?? 0;
|
||
final itemTaskId = treeId > 0 ? treeId : null;
|
||
final createTime = (json['discover'] as num?)?.toInt() ?? 0;
|
||
final createTimeText = json['uncover'] as String?;
|
||
final listingRaw = json['listing'];
|
||
final listingDisplay = listingDisplayFromApi(listingRaw);
|
||
final downsample = json['downsample'] as List<dynamic>? ?? [];
|
||
final items = <GalleryMediaItem>[];
|
||
// 只取downsample的array[0]
|
||
if (downsample.isNotEmpty) {
|
||
final first = downsample[0];
|
||
if (first is String && first.trim().isNotEmpty) {
|
||
items.add(GalleryMediaItem(
|
||
imageUrl: first,
|
||
taskId: itemTaskId,
|
||
createTime: createTime,
|
||
createTimeText: createTimeText,
|
||
listingDisplay: listingDisplay,
|
||
listingRaw: listingRaw,
|
||
));
|
||
} else if (first is Map<String, dynamic>) {
|
||
final reconfigure = first['reconfigure'] as String?;
|
||
if (reconfigure != null && reconfigure.isNotEmpty) {
|
||
final reconnect = first['reconnect'];
|
||
final imgType = reconnect is int
|
||
? reconnect
|
||
: reconnect is num
|
||
? reconnect.toInt()
|
||
: 1;
|
||
if (imgType == 2) {
|
||
items.add(GalleryMediaItem(
|
||
videoUrl: reconfigure,
|
||
taskId: itemTaskId,
|
||
createTime: createTime,
|
||
createTimeText: createTimeText,
|
||
listingDisplay: listingDisplay,
|
||
listingRaw: listingRaw,
|
||
));
|
||
} else {
|
||
items.add(GalleryMediaItem(
|
||
imageUrl: reconfigure,
|
||
taskId: itemTaskId,
|
||
createTime: createTime,
|
||
createTimeText: createTimeText,
|
||
listingDisplay: listingDisplay,
|
||
listingRaw: listingRaw,
|
||
));
|
||
}
|
||
}
|
||
}
|
||
// 后续下标均忽略
|
||
}
|
||
// downsample 无可用 URL 时仍占一格:用 tree 匹配本地封面缓存
|
||
if (items.isEmpty && itemTaskId != null && itemTaskId > 0) {
|
||
items.add(GalleryMediaItem(
|
||
taskId: itemTaskId,
|
||
createTime: createTime,
|
||
createTimeText: createTimeText,
|
||
listingDisplay: listingDisplay,
|
||
listingRaw: listingRaw,
|
||
));
|
||
}
|
||
// for (final item in downsample) {
|
||
// if (item is String) {
|
||
// items.add(GalleryMediaItem(imageUrl: item));
|
||
// } else if (item is Map<String, dynamic>) {
|
||
// final reconfigure = item['reconfigure'] as String?;
|
||
// if (reconfigure == null || reconfigure.isEmpty) continue;
|
||
// // reconnect(imgType): 0=视频,1=图片,其他默认当图片
|
||
// final reconnect = item['reconnect'];
|
||
// final imgType = reconnect is int
|
||
// ? reconnect
|
||
// : reconnect is num
|
||
// ? reconnect.toInt()
|
||
// : 1;
|
||
// if (imgType == 2) {
|
||
// items.add(GalleryMediaItem(videoUrl: reconfigure));
|
||
// } else {
|
||
// items.add(GalleryMediaItem(imageUrl: reconfigure));
|
||
// }
|
||
// }
|
||
// }
|
||
return GalleryTaskItem(
|
||
taskId: treeId,
|
||
state: json['listing']?.toString() ?? '',
|
||
taskType: (json['cipher'] as num?)?.toInt() ?? 0,
|
||
createTime: (json['discover'] as num?)?.toInt() ?? 0,
|
||
mediaItems: items,
|
||
);
|
||
}
|
||
}
|