Files
xianyan/lib/features/progress/progress_models.dart
Developer f91be94e9c refactor: 完成项目架构重构,统一模块导入路径
- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层
- 修复所有相对路径导入错误,统一调整为扁平化模块引用
- 更新多平台 pubspec 版本号与依赖库版本
- 补充后端功能问题管理后台与脚本工具
- 调整部分页面的快捷方式文案适配新功能
- 更新部分翻译覆盖率与API文档
2026-06-12 08:53:57 +08:00

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?,
);
}