Files
xianyan/lib/features/countdown/models/countdown_models.dart
Developer b5157c19f4 feat: 新增壁纸图库组件和编辑器功能优化
- 新增壁纸图库相关组件(WallpaperGalleryView/WallpaperSearchBar等)
- 优化编辑器主题服务和系统UI管理
- 新增虚线边框和拖拽描边风格支持
- 完善今日诗词服务和阅读报告功能
- 修复多个UI问题和空指针异常
- 更新依赖库版本和SVG资源
- 优化交互动画和状态管理
- 补充文档和API测试脚本
2026-05-05 05:03:33 +08:00

159 lines
4.5 KiB
Dart

/// ============================================================
/// 闲言APP — 倒计时数据模型
/// 创建时间: 2026-05-02
/// 更新时间: 2026-05-02
/// 作用: 倒计时事件模型 + 重复规则 + 分类
/// 上次更新: 初始创建
/// ============================================================
enum CountdownRepeat {
none('不重复', '🔄'),
daily('每天', '📅'),
weekly('每周', '📆'),
monthly('每月', '🗓️'),
yearly('每年', '🎂');
const CountdownRepeat(this.label, this.emoji);
final String label;
final String emoji;
}
enum CountdownCategory {
festival('节日', '🎉'),
birthday('生日', '🎂'),
anniversary('纪念日', '💝'),
exam('考试', '📝'),
travel('旅行', '✈️'),
deadline('截止日', ''),
custom('自定义', '📌');
const CountdownCategory(this.label, this.emoji);
final String label;
final String emoji;
}
class CountdownEvent {
const CountdownEvent({
required this.id,
required this.title,
required this.targetDate,
this.category = CountdownCategory.custom,
this.repeat = CountdownRepeat.none,
this.emoji = '📌',
this.colorHex = '#FF6B6B',
this.isPinned = false,
this.createdAt,
});
final String id;
final String title;
final DateTime targetDate;
final CountdownCategory category;
final CountdownRepeat repeat;
final String emoji;
final String colorHex;
final bool isPinned;
final DateTime? createdAt;
int get daysRemaining {
final now = DateTime.now();
final target = DateTime(targetDate.year, targetDate.month, targetDate.day);
final today = DateTime(now.year, now.month, now.day);
return target.difference(today).inDays;
}
bool get isPast => daysRemaining < 0;
bool get isToday => daysRemaining == 0;
String get remainingLabel {
if (isToday) return '今天';
if (isPast) return '已过 ${-daysRemaining}';
return '$daysRemaining';
}
DateTime? get nextOccurrence {
if (repeat == CountdownRepeat.none) return null;
final now = DateTime.now();
var next = targetDate;
switch (repeat) {
case CountdownRepeat.daily:
if (next.isBefore(now)) {
next = DateTime(now.year, now.month, now.day + 1);
}
case CountdownRepeat.weekly:
while (next.isBefore(now)) {
next = next.add(const Duration(days: 7));
}
case CountdownRepeat.monthly:
while (next.isBefore(now)) {
next = DateTime(next.year, next.month + 1, next.day);
}
case CountdownRepeat.yearly:
while (next.isBefore(now)) {
next = DateTime(next.year + 1, next.month, next.day);
}
case CountdownRepeat.none:
return null;
}
return next;
}
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'targetDate': targetDate.toIso8601String(),
'category': category.name,
'repeat': repeat.name,
'emoji': emoji,
'colorHex': colorHex,
'isPinned': isPinned,
'createdAt': createdAt?.toIso8601String(),
};
factory CountdownEvent.fromJson(Map<String, dynamic> json) =>
CountdownEvent(
id: json['id'] as String,
title: json['title'] as String,
targetDate: DateTime.parse(json['targetDate'] as String),
category: CountdownCategory.values.firstWhere(
(e) => e.name == json['category'],
orElse: () => CountdownCategory.custom,
),
repeat: CountdownRepeat.values.firstWhere(
(e) => e.name == json['repeat'],
orElse: () => CountdownRepeat.none,
),
emoji: json['emoji'] as String? ?? '📌',
colorHex: json['colorHex'] as String? ?? '#FF6B6B',
isPinned: json['isPinned'] as bool? ?? false,
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
: null,
);
CountdownEvent copyWith({
String? id,
String? title,
DateTime? targetDate,
CountdownCategory? category,
CountdownRepeat? repeat,
String? emoji,
String? colorHex,
bool? isPinned,
DateTime? createdAt,
}) {
return CountdownEvent(
id: id ?? this.id,
title: title ?? this.title,
targetDate: targetDate ?? this.targetDate,
category: category ?? this.category,
repeat: repeat ?? this.repeat,
emoji: emoji ?? this.emoji,
colorHex: colorHex ?? this.colorHex,
isPinned: isPinned ?? this.isPinned,
createdAt: createdAt ?? this.createdAt,
);
}
}