feat: 重构 Reminder Notification 系统并更新应用包名

This commit is contained in:
qzl
2026-03-30 18:36:57 +08:00
parent 9fb2a6857b
commit 91bf3c3f96
90 changed files with 5133 additions and 3017 deletions
@@ -0,0 +1,12 @@
enum ReminderAction { archive, snooze10m }
extension ReminderActionValue on ReminderAction {
String get value {
switch (this) {
case ReminderAction.archive:
return 'archive';
case ReminderAction.snooze10m:
return 'snooze10m';
}
}
}
@@ -0,0 +1,97 @@
class ReminderEventSnapshot {
const ReminderEventSnapshot({
required this.eventId,
required this.title,
required this.startAt,
required this.timezone,
required this.reminderMinutes,
this.endAt,
this.location,
this.notes,
this.isArchived = false,
});
final String eventId;
final String title;
final DateTime startAt;
final DateTime? endAt;
final String timezone;
final int? reminderMinutes;
final String? location;
final String? notes;
final bool isArchived;
}
class ReminderAlarm {
const ReminderAlarm({
required this.eventId,
required this.title,
required this.startAt,
required this.timezone,
required this.reminderMinutes,
required this.fireAt,
required this.fireTimeBucket,
this.endAt,
this.location,
this.notes,
this.version = 1,
});
final String eventId;
final String title;
final DateTime startAt;
final DateTime? endAt;
final String timezone;
final int reminderMinutes;
final DateTime fireAt;
final int fireTimeBucket;
final String? location;
final String? notes;
final int version;
Map<String, dynamic> toJson() {
return {
'eventId': eventId,
'title': title,
'startAt': startAt.toIso8601String(),
'endAt': endAt?.toIso8601String(),
'timezone': timezone,
'reminderMinutes': reminderMinutes,
'fireAt': fireAt.toIso8601String(),
'fireTimeBucket': fireTimeBucket,
'location': location,
'notes': notes,
'version': version,
};
}
factory ReminderAlarm.fromJson(Map<String, dynamic> json) {
return ReminderAlarm(
eventId: json['eventId'] as String,
title: json['title'] as String? ?? '',
startAt: DateTime.parse(json['startAt'] as String),
endAt: json['endAt'] == null
? null
: DateTime.parse(json['endAt'] as String),
timezone: json['timezone'] as String? ?? 'UTC',
reminderMinutes: json['reminderMinutes'] as int? ?? 0,
fireAt: DateTime.parse(json['fireAt'] as String),
fireTimeBucket: json['fireTimeBucket'] as int,
location: json['location'] as String?,
notes: json['notes'] as String?,
version: json['version'] as int? ?? 1,
);
}
}
class ReminderNotificationTap {
const ReminderNotificationTap({
required this.eventId,
required this.fireTimeBucket,
required this.payload,
});
final String eventId;
final int fireTimeBucket;
final Map<String, dynamic> payload;
}
@@ -1,182 +0,0 @@
class ReminderPayload {
final String eventId;
final String title;
final DateTime startAt;
final DateTime? endAt;
final String timezone;
final String? location;
final String? notes;
final String? color;
final ReminderPayloadMode mode;
final List<String> aggregateIds;
final int? fireTimeBucket;
final int version;
const ReminderPayload({
required this.eventId,
required this.title,
required this.startAt,
required this.timezone,
this.endAt,
this.location,
this.notes,
this.color,
this.mode = ReminderPayloadMode.single,
this.aggregateIds = const [],
this.fireTimeBucket,
this.version = 1,
});
ReminderPayload copyWith({
String? eventId,
String? title,
DateTime? startAt,
DateTime? endAt,
String? timezone,
String? location,
String? notes,
String? color,
ReminderPayloadMode? mode,
List<String>? aggregateIds,
int? fireTimeBucket,
int? version,
}) {
return ReminderPayload(
eventId: eventId ?? this.eventId,
title: title ?? this.title,
startAt: startAt ?? this.startAt,
endAt: endAt ?? this.endAt,
timezone: timezone ?? this.timezone,
location: location ?? this.location,
notes: notes ?? this.notes,
color: color ?? this.color,
mode: mode ?? this.mode,
aggregateIds: aggregateIds ?? this.aggregateIds,
fireTimeBucket: fireTimeBucket ?? this.fireTimeBucket,
version: version ?? this.version,
);
}
Map<String, dynamic> toJson() {
return {
'eventId': eventId,
'title': title,
'startAt': startAt.toIso8601String(),
'endAt': endAt?.toIso8601String(),
'timezone': timezone,
'location': location,
'notes': notes,
'color': color,
'mode': mode.value,
'aggregateIds': aggregateIds,
'fireTimeBucket': fireTimeBucket,
'version': version,
};
}
factory ReminderPayload.fromJson(Map<String, dynamic> json) {
final eventId = (json['eventId'] as String?) ?? '';
if (eventId.isEmpty) {
throw const FormatException('eventId is required');
}
final startAtRaw = json['startAt'] as String?;
if (startAtRaw == null || startAtRaw.isEmpty) {
throw const FormatException('startAt is required');
}
final parsedStartAt = DateTime.parse(startAtRaw);
final mode = ReminderPayloadMode.fromValue(
(json['mode'] as String?) ?? 'single',
);
final aggregateIds = (json['aggregateIds'] as List<dynamic>? ?? const [])
.map((item) => item.toString())
.toList();
if (mode == ReminderPayloadMode.aggregate && aggregateIds.length < 2) {
throw const FormatException('aggregateIds must contain at least 2 items');
}
return ReminderPayload(
eventId: eventId,
title: (json['title'] as String?) ?? '',
startAt: parsedStartAt,
endAt: json['endAt'] != null
? DateTime.parse(json['endAt'] as String)
: null,
timezone: (json['timezone'] as String?) ?? 'UTC',
location: json['location'] as String?,
notes: json['notes'] as String?,
color: json['color'] as String?,
mode: mode,
aggregateIds: aggregateIds,
fireTimeBucket: json['fireTimeBucket'] as int?,
version: (json['version'] as int?) ?? 1,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is ReminderPayload &&
other.eventId == eventId &&
other.title == title &&
other.startAt == startAt &&
other.endAt == endAt &&
other.timezone == timezone &&
other.location == location &&
other.notes == notes &&
other.color == color &&
other.mode == mode &&
_listEquals(other.aggregateIds, aggregateIds) &&
other.fireTimeBucket == fireTimeBucket &&
other.version == version;
}
@override
int get hashCode {
return Object.hash(
eventId,
title,
startAt,
endAt,
timezone,
location,
notes,
color,
mode,
Object.hashAll(aggregateIds),
fireTimeBucket,
version,
);
}
}
enum ReminderPayloadMode {
single('single'),
aggregate('aggregate');
const ReminderPayloadMode(this.value);
final String value;
static ReminderPayloadMode fromValue(String raw) {
return ReminderPayloadMode.values.firstWhere(
(item) => item.value == raw,
orElse: () => ReminderPayloadMode.single,
);
}
}
bool _listEquals(List<String> left, List<String> right) {
if (left.length != right.length) {
return false;
}
for (var i = 0; i < left.length; i++) {
if (left[i] != right[i]) {
return false;
}
}
return true;
}