313 lines
8.4 KiB
Dart
313 lines
8.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
enum ScheduleSourceType { manual, imported, agentGenerated }
|
|
|
|
enum ScheduleStatus { active, completed, canceled, archived }
|
|
|
|
class ScheduleItemModel {
|
|
final String id;
|
|
final String ownerId;
|
|
final int permission;
|
|
final bool isOwner;
|
|
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;
|
|
|
|
static const int PERMISSION_VIEW = 1;
|
|
static const int PERMISSION_INVITE = 2;
|
|
static const int PERMISSION_EDIT = 4;
|
|
|
|
bool get canEdit => isOwner || (permission & PERMISSION_EDIT) != 0;
|
|
bool get canInvite => isOwner || (permission & PERMISSION_INVITE) != 0;
|
|
bool get canDelete => isOwner;
|
|
|
|
ScheduleItemModel({
|
|
required this.id,
|
|
required this.ownerId,
|
|
this.permission = 1,
|
|
this.isOwner = false,
|
|
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,
|
|
}) : createdAt = createdAt ?? DateTime.now(),
|
|
updatedAt = updatedAt ?? DateTime.now();
|
|
|
|
ScheduleItemModel copyWith({
|
|
String? id,
|
|
String? ownerId,
|
|
int? permission,
|
|
bool? isOwner,
|
|
String? title,
|
|
String? description,
|
|
DateTime? startAt,
|
|
DateTime? endAt,
|
|
String? timezone,
|
|
ScheduleMetadata? metadata,
|
|
ScheduleSourceType? sourceType,
|
|
ScheduleStatus? status,
|
|
DateTime? createdAt,
|
|
DateTime? updatedAt,
|
|
}) {
|
|
return ScheduleItemModel(
|
|
id: id ?? this.id,
|
|
ownerId: ownerId ?? this.ownerId,
|
|
permission: permission ?? this.permission,
|
|
isOwner: isOwner ?? this.isOwner,
|
|
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,
|
|
);
|
|
}
|
|
|
|
factory ScheduleItemModel.fromJson(Map<String, dynamic> json) {
|
|
return ScheduleItemModel(
|
|
id: json['id'] as String,
|
|
ownerId: json['owner_id'] as String? ?? '',
|
|
permission: json['permission'] as int? ?? 1,
|
|
isOwner: json['is_owner'] as bool? ?? false,
|
|
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?) ?? 'UTC',
|
|
metadata: json['metadata'] is Map<String, dynamic>
|
|
? ScheduleMetadata.fromJson(json['metadata'] as Map<String, dynamic>)
|
|
: null,
|
|
sourceType: _sourceTypeFromApi(json['source_type'] as String?),
|
|
status: _statusFromApi(json['status'] as String?),
|
|
createdAt: json['created_at'] != null
|
|
? DateTime.parse(json['created_at'] as String).toLocal()
|
|
: DateTime.now(),
|
|
updatedAt: json['updated_at'] != null
|
|
? DateTime.parse(json['updated_at'] as String).toLocal()
|
|
: DateTime.now(),
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toCreateJson() {
|
|
return {
|
|
'title': title,
|
|
'description': description,
|
|
'start_at': startAt.toUtc().toIso8601String(),
|
|
'end_at': endAt?.toUtc().toIso8601String(),
|
|
'timezone': timezone,
|
|
'metadata': metadata?.toJson(),
|
|
};
|
|
}
|
|
|
|
Map<String, dynamic> 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<Attachment> attachments;
|
|
final int version;
|
|
final Map<String, dynamic> raw;
|
|
|
|
ScheduleMetadata({
|
|
this.color,
|
|
this.location,
|
|
this.notes,
|
|
this.reminderMinutes,
|
|
List<Attachment>? attachments,
|
|
this.version = 1,
|
|
Map<String, dynamic>? raw,
|
|
}) : attachments = attachments ?? const [],
|
|
raw = raw ?? const {};
|
|
|
|
ScheduleMetadata copyWith({
|
|
String? color,
|
|
String? location,
|
|
String? notes,
|
|
int? reminderMinutes,
|
|
List<Attachment>? attachments,
|
|
int? version,
|
|
Map<String, dynamic>? 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<String, dynamic> json) {
|
|
final rawAttachments = json['attachments'];
|
|
final attachments = rawAttachments is List
|
|
? rawAttachments
|
|
.whereType<Map<String, dynamic>>()
|
|
.map(Attachment.fromJson)
|
|
.toList()
|
|
: <Attachment>[];
|
|
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?) ?? 1,
|
|
raw: Map<String, dynamic>.from(json),
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> 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<String> 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<String>? 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<String, dynamic> json) {
|
|
final rawVisibleTo = json['visible_to'];
|
|
final visibleTo = rawVisibleTo is List
|
|
? rawVisibleTo.map((item) => item.toString()).toList()
|
|
: <String>[];
|
|
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?) ?? 'document',
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> 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':
|
|
default:
|
|
return ScheduleSourceType.manual;
|
|
}
|
|
}
|
|
|
|
ScheduleStatus _statusFromApi(String? raw) {
|
|
switch (raw) {
|
|
case 'completed':
|
|
return ScheduleStatus.completed;
|
|
case 'canceled':
|
|
return ScheduleStatus.canceled;
|
|
case 'archived':
|
|
return ScheduleStatus.archived;
|
|
case 'active':
|
|
default:
|
|
return ScheduleStatus.active;
|
|
}
|
|
}
|
|
|
|
String _statusToApi(ScheduleStatus status) {
|
|
switch (status) {
|
|
case ScheduleStatus.active:
|
|
return 'active';
|
|
case ScheduleStatus.completed:
|
|
return 'completed';
|
|
case ScheduleStatus.canceled:
|
|
return 'canceled';
|
|
case ScheduleStatus.archived:
|
|
return 'archived';
|
|
}
|
|
}
|
|
|
|
const defaultColors = [
|
|
Color(0xFF3B82F6),
|
|
Color(0xFF8B5CF6),
|
|
Color(0xFF10B981),
|
|
Color(0xFFF59E0B),
|
|
Color(0xFFEF4444),
|
|
];
|