- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层 - 修复所有相对路径导入错误,统一调整为扁平化模块引用 - 更新多平台 pubspec 版本号与依赖库版本 - 补充后端功能问题管理后台与脚本工具 - 调整部分页面的快捷方式文案适配新功能 - 更新部分翻译覆盖率与API文档
303 lines
8.7 KiB
Dart
303 lines
8.7 KiB
Dart
/// ============================================================
|
|
/// 闲言APP — 进度页面数据模型
|
|
/// 创建时间: 2026-05-02
|
|
/// 更新时间: 2026-05-30
|
|
/// 作用: 进度会话页面数据结构 — 时间进度/节日倒计时/用户自定义
|
|
/// 上次更新: ProgressDisplayStyle增加label/emoji/iconName属性
|
|
/// ============================================================
|
|
|
|
enum ProgressItemType {
|
|
timeProgress('time_progress', '时间进度'),
|
|
holidayCountdown('holiday_countdown', '节日倒计时'),
|
|
userCountdown('user_countdown', '用户倒计时'),
|
|
userProgress('user_progress', '用户进度');
|
|
|
|
const ProgressItemType(this.id, this.label);
|
|
final String id;
|
|
final String label;
|
|
|
|
bool get isUserCreated =>
|
|
this == ProgressItemType.userCountdown ||
|
|
this == ProgressItemType.userProgress;
|
|
}
|
|
|
|
enum ProgressDisplayStyle {
|
|
progressBar('progress_bar', '进度条', '📊', 'chart_bar_fill'),
|
|
ringProgress('ring_progress', '环形进度', '⭕', 'circle_grid_3x3_fill'),
|
|
countdownGrid('countdown_grid', '倒计时网格', '⏱️', 'timer'),
|
|
tagOnly('tag_only', '仅标签', '🏷️', 'tag_fill');
|
|
|
|
const ProgressDisplayStyle(this.id, this.label, this.emoji, this.iconName);
|
|
final String id;
|
|
final String label;
|
|
final String emoji;
|
|
final String iconName;
|
|
}
|
|
|
|
class ProgressItem {
|
|
const ProgressItem({
|
|
required this.id,
|
|
required this.type,
|
|
required this.emoji,
|
|
required this.title,
|
|
required this.subtitle,
|
|
this.targetDate,
|
|
this.progressPct,
|
|
this.tagText,
|
|
this.tagColor,
|
|
this.displayStyle = ProgressDisplayStyle.countdownGrid,
|
|
this.isPast = false,
|
|
this.createdAt,
|
|
this.statItems = const [],
|
|
this.summaryRows = const [],
|
|
this.ringPct,
|
|
this.ringLabel,
|
|
this.ringColor,
|
|
this.note,
|
|
this.milestones = const [],
|
|
this.history = const [],
|
|
});
|
|
|
|
final String id;
|
|
final ProgressItemType type;
|
|
final String emoji;
|
|
final String title;
|
|
final String subtitle;
|
|
final DateTime? targetDate;
|
|
final double? progressPct;
|
|
final String? tagText;
|
|
final String? tagColor;
|
|
final ProgressDisplayStyle displayStyle;
|
|
final bool isPast;
|
|
final DateTime? createdAt;
|
|
final List<ProgressStatItem> statItems;
|
|
final List<ProgressSummaryRow> summaryRows;
|
|
final double? ringPct;
|
|
final String? ringLabel;
|
|
final String? ringColor;
|
|
final String? note;
|
|
final List<ProgressMilestone> milestones;
|
|
final List<ProgressHistoryPoint> history;
|
|
|
|
Duration? get remaining {
|
|
if (targetDate == null) return null;
|
|
final diff = targetDate!.difference(DateTime.now());
|
|
return diff.isNegative ? Duration.zero : diff;
|
|
}
|
|
|
|
int get remainingDays => remaining?.inDays ?? 0;
|
|
int get remainingHours => remaining != null ? (remaining!.inHours % 24) : 0;
|
|
int get remainingMinutes =>
|
|
remaining != null ? (remaining!.inMinutes % 60) : 0;
|
|
int get remainingSeconds =>
|
|
remaining != null ? (remaining!.inSeconds % 60) : 0;
|
|
|
|
ProgressItem copyWith({
|
|
String? id,
|
|
ProgressItemType? type,
|
|
String? emoji,
|
|
String? title,
|
|
String? subtitle,
|
|
DateTime? targetDate,
|
|
double? progressPct,
|
|
String? tagText,
|
|
String? tagColor,
|
|
ProgressDisplayStyle? displayStyle,
|
|
bool? isPast,
|
|
DateTime? createdAt,
|
|
List<ProgressStatItem>? statItems,
|
|
List<ProgressSummaryRow>? summaryRows,
|
|
double? ringPct,
|
|
String? ringLabel,
|
|
String? ringColor,
|
|
String? note,
|
|
List<ProgressMilestone>? milestones,
|
|
List<ProgressHistoryPoint>? history,
|
|
}) {
|
|
return ProgressItem(
|
|
id: id ?? this.id,
|
|
type: type ?? this.type,
|
|
emoji: emoji ?? this.emoji,
|
|
title: title ?? this.title,
|
|
subtitle: subtitle ?? this.subtitle,
|
|
targetDate: targetDate ?? this.targetDate,
|
|
progressPct: progressPct ?? this.progressPct,
|
|
tagText: tagText ?? this.tagText,
|
|
tagColor: tagColor ?? this.tagColor,
|
|
displayStyle: displayStyle ?? this.displayStyle,
|
|
isPast: isPast ?? this.isPast,
|
|
createdAt: createdAt ?? this.createdAt,
|
|
statItems: statItems ?? this.statItems,
|
|
summaryRows: summaryRows ?? this.summaryRows,
|
|
ringPct: ringPct ?? this.ringPct,
|
|
ringLabel: ringLabel ?? this.ringLabel,
|
|
ringColor: ringColor ?? this.ringColor,
|
|
note: note ?? this.note,
|
|
milestones: milestones ?? this.milestones,
|
|
history: history ?? this.history,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'type': type.id,
|
|
'emoji': emoji,
|
|
'title': title,
|
|
'subtitle': subtitle,
|
|
'targetDate': targetDate?.toIso8601String(),
|
|
'progressPct': progressPct,
|
|
'tagText': tagText,
|
|
'tagColor': tagColor,
|
|
'displayStyle': displayStyle.index,
|
|
'isPast': isPast,
|
|
'createdAt': createdAt?.toIso8601String(),
|
|
'note': note,
|
|
'milestones': milestones.map((m) => m.toJson()).toList(),
|
|
'history': history.map((h) => h.toJson()).toList(),
|
|
};
|
|
|
|
factory ProgressItem.fromJson(Map<String, dynamic> json) => ProgressItem(
|
|
id: json['id'] as String? ?? '',
|
|
type: ProgressItemType.values.firstWhere(
|
|
(e) => e.id == json['type'],
|
|
orElse: () => ProgressItemType.userCountdown,
|
|
),
|
|
emoji: json['emoji'] as String? ?? '📝',
|
|
title: json['title'] as String? ?? '',
|
|
subtitle: json['subtitle'] as String? ?? '',
|
|
targetDate: json['targetDate'] != null
|
|
? DateTime.parse(json['targetDate'] as String)
|
|
: null,
|
|
progressPct: (json['progressPct'] as num?)?.toDouble(),
|
|
tagText: json['tagText'] as String?,
|
|
tagColor: json['tagColor'] as String?,
|
|
displayStyle:
|
|
ProgressDisplayStyle.values[json['displayStyle'] as int? ?? 0],
|
|
isPast: json['isPast'] as bool? ?? false,
|
|
createdAt: json['createdAt'] != null
|
|
? DateTime.parse(json['createdAt'] as String)
|
|
: null,
|
|
note: json['note'] as String?,
|
|
milestones:
|
|
(json['milestones'] as List<dynamic>?)
|
|
?.map((e) => ProgressMilestone.fromJson(e as Map<String, dynamic>))
|
|
.toList() ??
|
|
[],
|
|
history:
|
|
(json['history'] as List<dynamic>?)
|
|
?.map(
|
|
(e) => ProgressHistoryPoint.fromJson(e as Map<String, dynamic>),
|
|
)
|
|
.toList() ??
|
|
[],
|
|
);
|
|
}
|
|
|
|
class ProgressStatItem {
|
|
const ProgressStatItem({required this.value, required this.label});
|
|
final String value;
|
|
final String label;
|
|
}
|
|
|
|
class ProgressSummaryRow {
|
|
const ProgressSummaryRow({required this.label, required this.value});
|
|
final String label;
|
|
final String value;
|
|
}
|
|
|
|
class ProgressTimeDivider {
|
|
const ProgressTimeDivider({required this.text, this.emoji});
|
|
final String text;
|
|
final String? emoji;
|
|
}
|
|
|
|
/// 进度排序模式
|
|
enum ProgressSortMode {
|
|
custom('custom', '自定义', '✋'),
|
|
byUrgency('urgency', '紧急程度', '🔥'),
|
|
byDate('date', '创建时间', '📅'),
|
|
byType('type', '按类型', '📂');
|
|
|
|
const ProgressSortMode(this.id, this.label, this.emoji);
|
|
final String id;
|
|
final String label;
|
|
final String emoji;
|
|
|
|
static ProgressSortMode fromId(String id) {
|
|
return ProgressSortMode.values.firstWhere(
|
|
(e) => e.id == id,
|
|
orElse: () => ProgressSortMode.custom,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 里程碑节点
|
|
class ProgressMilestone {
|
|
const ProgressMilestone({
|
|
required this.label,
|
|
required this.targetDate,
|
|
this.emoji = '🏁',
|
|
this.isReached = false,
|
|
});
|
|
|
|
final String label;
|
|
final DateTime targetDate;
|
|
final String emoji;
|
|
final bool isReached;
|
|
|
|
ProgressMilestone copyWith({
|
|
String? label,
|
|
DateTime? targetDate,
|
|
String? emoji,
|
|
bool? isReached,
|
|
}) {
|
|
return ProgressMilestone(
|
|
label: label ?? this.label,
|
|
targetDate: targetDate ?? this.targetDate,
|
|
emoji: emoji ?? this.emoji,
|
|
isReached: isReached ?? this.isReached,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'label': label,
|
|
'targetDate': targetDate.toIso8601String(),
|
|
'emoji': emoji,
|
|
'isReached': isReached,
|
|
};
|
|
|
|
factory ProgressMilestone.fromJson(Map<String, dynamic> json) =>
|
|
ProgressMilestone(
|
|
label: json['label'] as String? ?? '',
|
|
targetDate: DateTime.parse(json['targetDate'] as String),
|
|
emoji: json['emoji'] as String? ?? '🏁',
|
|
isReached: json['isReached'] as bool? ?? false,
|
|
);
|
|
}
|
|
|
|
/// 进度历史记录点
|
|
class ProgressHistoryPoint {
|
|
const ProgressHistoryPoint({
|
|
required this.date,
|
|
required this.progressPct,
|
|
this.note,
|
|
});
|
|
|
|
final DateTime date;
|
|
final double progressPct;
|
|
final String? note;
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'date': date.toIso8601String(),
|
|
'progressPct': progressPct,
|
|
'note': note,
|
|
};
|
|
|
|
factory ProgressHistoryPoint.fromJson(Map<String, dynamic> json) =>
|
|
ProgressHistoryPoint(
|
|
date: DateTime.parse(json['date'] as String),
|
|
progressPct: (json['progressPct'] as num?)?.toDouble() ?? 0.0,
|
|
note: json['note'] as String?,
|
|
);
|
|
}
|