- 新增壁纸图库相关组件(WallpaperGalleryView/WallpaperSearchBar等) - 优化编辑器主题服务和系统UI管理 - 新增虚线边框和拖拽描边风格支持 - 完善今日诗词服务和阅读报告功能 - 修复多个UI问题和空指针异常 - 更新依赖库版本和SVG资源 - 优化交互动画和状态管理 - 补充文档和API测试脚本
159 lines
4.5 KiB
Dart
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,
|
|
);
|
|
}
|
|
}
|