enum ScheduleSourceType { manual, imported, agentGenerated } enum ScheduleStatus { active, archived } class Subscriber { final String userId; final String? username; final String? avatarUrl; final String? phone; final int permission; final String status; final DateTime subscribedAt; static const int permissionView = 1; static const int permissionInvite = 2; static const int permissionEdit = 4; static const int permissionDelete = 8; static const int permissionOwner = 15; bool get canEdit => (permission & permissionEdit) != 0; bool get canInvite => (permission & permissionInvite) != 0; bool get canDelete => (permission & permissionDelete) != 0; bool get canView => (permission & permissionView) != 0; Subscriber({ required this.userId, this.username, this.avatarUrl, this.phone, required this.permission, required this.status, required this.subscribedAt, }); factory Subscriber.fromJson(Map json) { return Subscriber( userId: json['user_id'] as String, username: json['username'] as String?, avatarUrl: json['avatar_url'] as String?, phone: json['phone'] as String?, permission: json['permission'] as int, status: json['status'] as String, subscribedAt: DateTime.parse(json['subscribed_at'] as String).toLocal(), ); } } class ScheduleItemModel { final String id; final String ownerId; final int permission; final String title; final String? description; final DateTime startAt; final DateTime? endAt; final String timezone; final ScheduleMetadata? metadata; final ScheduleSourceType sourceType; final ScheduleStatus status; final DateTime createdAt; final DateTime updatedAt; final List subscribers; static const int permissionView = 1; static const int permissionInvite = 2; static const int permissionEdit = 4; static const int permissionDelete = 8; static const int permissionOwner = 15; bool get canEdit => (permission & permissionEdit) != 0; bool get canInvite => (permission & permissionInvite) != 0; bool get canDelete => (permission & permissionDelete) != 0; bool get isOwner => (permission & permissionOwner) != 0; ScheduleItemModel({ required this.id, required this.ownerId, this.permission = 1, required this.title, this.description, required this.startAt, this.endAt, this.timezone = 'Asia/Shanghai', this.metadata, this.sourceType = ScheduleSourceType.manual, this.status = ScheduleStatus.active, DateTime? createdAt, DateTime? updatedAt, this.subscribers = const [], }) : createdAt = createdAt ?? DateTime.now(), updatedAt = updatedAt ?? DateTime.now(); ScheduleItemModel copyWith({ String? id, String? ownerId, int? permission, String? title, String? description, DateTime? startAt, DateTime? endAt, String? timezone, ScheduleMetadata? metadata, ScheduleSourceType? sourceType, ScheduleStatus? status, DateTime? createdAt, DateTime? updatedAt, List? subscribers, }) { return ScheduleItemModel( id: id ?? this.id, ownerId: ownerId ?? this.ownerId, permission: permission ?? this.permission, title: title ?? this.title, description: description ?? this.description, startAt: startAt ?? this.startAt, endAt: endAt ?? this.endAt, timezone: timezone ?? this.timezone, metadata: metadata ?? this.metadata, sourceType: sourceType ?? this.sourceType, status: status ?? this.status, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, subscribers: subscribers ?? this.subscribers, ); } factory ScheduleItemModel.fromJson(Map json) { final subscribersList = json['subscribers'] as List?; final subscribers = subscribersList ?.map((s) => Subscriber.fromJson(s as Map)) .toList() ?? []; return ScheduleItemModel( id: json['id'] as String, ownerId: json['owner_id'] as String, permission: json['permission'] as int, title: json['title'] as String, description: json['description'] as String?, startAt: DateTime.parse(json['start_at'] as String).toLocal(), endAt: json['end_at'] != null ? DateTime.parse(json['end_at'] as String).toLocal() : null, timezone: json['timezone'] as String, metadata: json['metadata'] is Map ? ScheduleMetadata.fromJson(json['metadata'] as Map) : null, sourceType: _sourceTypeFromApi(json['source_type'] as String), status: _statusFromApi(json['status'] as String), createdAt: DateTime.parse(json['created_at'] as String).toLocal(), updatedAt: DateTime.parse(json['updated_at'] as String).toLocal(), subscribers: subscribers, ); } Map toCreateJson() { return { 'title': title, 'description': description, 'start_at': startAt.toUtc().toIso8601String(), 'end_at': endAt?.toUtc().toIso8601String(), 'timezone': timezone, 'metadata': metadata?.toJson(), }; } Map toUpdateJson() { return { 'title': title, 'description': description, 'start_at': startAt.toUtc().toIso8601String(), 'end_at': endAt?.toUtc().toIso8601String(), 'timezone': timezone, 'metadata': metadata?.toJson(), 'status': _statusToApi(status), }; } } class ScheduleMetadata { final String? color; final String? location; final String? notes; final int? reminderMinutes; final List attachments; final int version; final Map raw; ScheduleMetadata({ this.color, this.location, this.notes, this.reminderMinutes, List? attachments, this.version = 1, Map? raw, }) : attachments = attachments ?? const [], raw = raw ?? const {}; ScheduleMetadata copyWith({ String? color, String? location, String? notes, int? reminderMinutes, List? attachments, int? version, Map? raw, }) { return ScheduleMetadata( color: color ?? this.color, location: location ?? this.location, notes: notes ?? this.notes, reminderMinutes: reminderMinutes ?? this.reminderMinutes, attachments: attachments ?? this.attachments, version: version ?? this.version, raw: raw ?? this.raw, ); } factory ScheduleMetadata.fromJson(Map json) { final rawAttachments = json['attachments'] as List; final attachments = rawAttachments .map((item) => Attachment.fromJson(item as Map)) .toList(growable: false); return ScheduleMetadata( color: json['color'] as String?, location: json['location'] as String?, notes: json['notes'] as String?, reminderMinutes: json['reminder_minutes'] as int?, attachments: attachments, version: json['version'] as int, raw: Map.from(json), ); } Map toJson() { return { 'color': color, 'location': location, 'notes': notes, 'reminder_minutes': reminderMinutes, 'attachments': attachments.map((item) => item.toJson()).toList(), 'version': version, }; } } class Attachment { final String name; final List visibleTo; final String? url; final String? note; final String? content; final String type; Attachment({ required this.name, this.visibleTo = const [], this.url, this.note, this.content, this.type = 'document', }); Attachment copyWith({ String? name, List? visibleTo, String? url, String? note, String? content, String? type, }) { return Attachment( name: name ?? this.name, visibleTo: visibleTo ?? this.visibleTo, url: url ?? this.url, note: note ?? this.note, content: content ?? this.content, type: type ?? this.type, ); } factory Attachment.fromJson(Map json) { final rawVisibleTo = json['visible_to'] as List; final visibleTo = rawVisibleTo.map((item) => item.toString()).toList(); return Attachment( name: json['name'] as String, visibleTo: visibleTo, url: json['url'] as String?, note: json['note'] as String?, content: json['content'] as String?, type: json['type'] as String, ); } Map toJson() { return { 'name': name, 'visible_to': visibleTo, 'url': url, 'note': note, 'content': content, 'type': type, }; } } ScheduleSourceType _sourceTypeFromApi(String raw) { switch (raw) { case 'imported': return ScheduleSourceType.imported; case 'agent_generated': return ScheduleSourceType.agentGenerated; case 'manual': return ScheduleSourceType.manual; default: throw StateError('Unsupported schedule source type: $raw'); } } ScheduleStatus _statusFromApi(String raw) { switch (raw) { case 'archived': return ScheduleStatus.archived; case 'active': return ScheduleStatus.active; default: throw StateError('Unsupported schedule status: $raw'); } } String _statusToApi(ScheduleStatus status) { switch (status) { case ScheduleStatus.active: return 'active'; case ScheduleStatus.archived: return 'archived'; } }