c3192a2431
- Add ChatBubble reusable widget for chat messages - Add HomeMockData for chat list mock data - Add HomeScreen widget tests - Add AG-UI chat design and implementation plan docs - Add friendship design docs - Ignore backend/logs directory
296 lines
8.4 KiB
Dart
296 lines
8.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../../shared/widgets/chat_bubble.dart';
|
|
|
|
enum ChatItemType { message, schedule }
|
|
|
|
abstract class ChatListItem {
|
|
String get id;
|
|
DateTime get timestamp;
|
|
ChatItemType get type;
|
|
MessageSender get sender;
|
|
}
|
|
|
|
class HomeMockData {
|
|
static List<ChatListItem> getTodayItems() {
|
|
final now = DateTime.now();
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
return _getMockItems().where((item) {
|
|
final itemDate = DateTime(
|
|
item.timestamp.year,
|
|
item.timestamp.month,
|
|
item.timestamp.day,
|
|
);
|
|
return itemDate == today;
|
|
}).toList();
|
|
}
|
|
|
|
static Future<List<ChatListItem>> loadMoreItems(DateTime beforeDate) async {
|
|
return _getOlderMockItems(beforeDate);
|
|
}
|
|
|
|
static List<ChatListItem> _getMockItems() {
|
|
final now = DateTime.now();
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
final todayStart = DateTime(today.year, today.month, today.day);
|
|
|
|
return [
|
|
ChatMessageItem(
|
|
id: 'm4',
|
|
content: '明天提醒我开会',
|
|
timestamp: todayStart.add(const Duration(hours: 14)),
|
|
sender: MessageSender.user,
|
|
),
|
|
ScheduleItemWrapper(
|
|
id: 's1',
|
|
scheduleItem: ScheduleItemModel(
|
|
id: 's1',
|
|
title: '产品评审会议',
|
|
description: '讨论Q2产品路线图',
|
|
startAt: todayStart.add(const Duration(days: 1, hours: 10)),
|
|
endAt: todayStart.add(const Duration(days: 1, hours: 11)),
|
|
timezone: 'Asia/Shanghai',
|
|
sourceType: ScheduleSourceType.agentGenerated,
|
|
status: ScheduleStatus.active,
|
|
metadata: ScheduleMetadata(
|
|
color: '#4F46E5',
|
|
location: '会议室A / 在线',
|
|
notes: '需要提前准备Q2数据',
|
|
attachments: [
|
|
Attachment(
|
|
name: 'Q2路线图.pdf',
|
|
type: AttachmentType.document,
|
|
url: 'https://example.com/q2.pdf',
|
|
),
|
|
],
|
|
),
|
|
createdAt: todayStart.subtract(const Duration(hours: 5)),
|
|
),
|
|
timestamp: todayStart.add(const Duration(hours: 14, minutes: 2)),
|
|
sender: MessageSender.ai,
|
|
),
|
|
ChatMessageItem(
|
|
id: 'm5',
|
|
content: '已为你创建日程"产品评审会议",明天上午10:00。我还会提前15分钟提醒你。',
|
|
timestamp: todayStart.add(const Duration(hours: 14, minutes: 2)),
|
|
sender: MessageSender.ai,
|
|
),
|
|
];
|
|
}
|
|
|
|
static List<ChatListItem> _getOlderMockItems(DateTime beforeDate) {
|
|
final before = DateTime(beforeDate.year, beforeDate.month, beforeDate.day);
|
|
final dayBefore = before.subtract(const Duration(days: 1));
|
|
|
|
return [
|
|
ChatMessageItem(
|
|
id: 'm1',
|
|
content: '你好,我有什么可以帮你的?',
|
|
timestamp: dayBefore.add(const Duration(hours: 10)),
|
|
sender: MessageSender.ai,
|
|
),
|
|
ChatMessageItem(
|
|
id: 'm2',
|
|
content: '下周一之前提交项目报告',
|
|
timestamp: dayBefore.add(const Duration(hours: 9, minutes: 55)),
|
|
sender: MessageSender.user,
|
|
),
|
|
ScheduleItemWrapper(
|
|
id: 's0',
|
|
scheduleItem: ScheduleItemModel(
|
|
id: 's0',
|
|
title: '提交项目报告',
|
|
description: '完成并提交Q2项目报告',
|
|
startAt: before.subtract(const Duration(days: 3)),
|
|
endAt: null,
|
|
timezone: 'Asia/Shanghai',
|
|
sourceType: ScheduleSourceType.agentGenerated,
|
|
status: ScheduleStatus.active,
|
|
metadata: ScheduleMetadata(
|
|
color: '#F59E0B',
|
|
location: null,
|
|
notes: '记得附上数据附件',
|
|
attachments: [],
|
|
),
|
|
createdAt: dayBefore.add(const Duration(hours: 9, minutes: 50)),
|
|
),
|
|
timestamp: dayBefore.add(const Duration(hours: 9, minutes: 50)),
|
|
sender: MessageSender.ai,
|
|
),
|
|
ChatMessageItem(
|
|
id: 'm3',
|
|
content: '好的,我已帮你创建待办事项"提交项目报告",截止日期为下周一。我还会提醒你完成这项任务。',
|
|
timestamp: dayBefore.add(const Duration(hours: 9, minutes: 50)),
|
|
sender: MessageSender.ai,
|
|
),
|
|
];
|
|
}
|
|
}
|
|
|
|
class ChatMessageItem extends ChatListItem {
|
|
@override
|
|
final String id;
|
|
final String content;
|
|
@override
|
|
final DateTime timestamp;
|
|
@override
|
|
final MessageSender sender;
|
|
|
|
ChatMessageItem({
|
|
required this.id,
|
|
required this.content,
|
|
required this.timestamp,
|
|
required this.sender,
|
|
});
|
|
|
|
@override
|
|
ChatItemType get type => ChatItemType.message;
|
|
}
|
|
|
|
class ScheduleItemWrapper extends ChatListItem {
|
|
@override
|
|
final String id;
|
|
final ScheduleItemModel scheduleItem;
|
|
@override
|
|
final DateTime timestamp;
|
|
@override
|
|
final MessageSender sender;
|
|
|
|
ScheduleItemWrapper({
|
|
required this.id,
|
|
required this.scheduleItem,
|
|
required this.timestamp,
|
|
required this.sender,
|
|
});
|
|
|
|
@override
|
|
ChatItemType get type => ChatItemType.schedule;
|
|
}
|
|
|
|
enum ScheduleSourceType { manual, imported, agentGenerated }
|
|
|
|
enum ScheduleStatus { active, completed, canceled, archived }
|
|
|
|
class ScheduleItemModel {
|
|
final String id;
|
|
final String title;
|
|
final String? description;
|
|
final DateTime startAt;
|
|
final DateTime? endAt;
|
|
final String timezone;
|
|
final ScheduleSourceType sourceType;
|
|
final ScheduleStatus status;
|
|
final ScheduleMetadata? metadata;
|
|
final DateTime createdAt;
|
|
|
|
ScheduleItemModel({
|
|
required this.id,
|
|
required this.title,
|
|
this.description,
|
|
required this.startAt,
|
|
this.endAt,
|
|
required this.timezone,
|
|
required this.sourceType,
|
|
required this.status,
|
|
this.metadata,
|
|
required this.createdAt,
|
|
});
|
|
|
|
factory ScheduleItemModel.fromJson(Map<String, dynamic> json) {
|
|
return ScheduleItemModel(
|
|
id: json['id'],
|
|
title: json['title'],
|
|
description: json['description'],
|
|
startAt: DateTime.parse(json['start_at']),
|
|
endAt: json['end_at'] != null ? DateTime.parse(json['end_at']) : null,
|
|
timezone: json['timezone'] ?? 'UTC',
|
|
sourceType: ScheduleSourceType.values.firstWhere(
|
|
(e) => e.name == json['source_type'],
|
|
orElse: () => ScheduleSourceType.manual,
|
|
),
|
|
status: ScheduleStatus.values.firstWhere(
|
|
(e) => e.name == json['status'],
|
|
orElse: () => ScheduleStatus.active,
|
|
),
|
|
metadata: json['metadata'] != null
|
|
? ScheduleMetadata(
|
|
color: json['metadata']['color'],
|
|
location: json['metadata']['location'],
|
|
notes: json['metadata']['notes'],
|
|
attachments:
|
|
(json['metadata']['attachments'] as List<dynamic>?)
|
|
?.map(
|
|
(a) => Attachment(
|
|
name: a['name'],
|
|
type: a['type'] == 'document'
|
|
? AttachmentType.document
|
|
: AttachmentType.reminder,
|
|
url: a['url'],
|
|
content: a['content'],
|
|
note: a['note'],
|
|
),
|
|
)
|
|
.toList() ??
|
|
[],
|
|
)
|
|
: null,
|
|
createdAt: DateTime.parse(json['created_at']),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ScheduleMetadata {
|
|
final String? color;
|
|
final String? location;
|
|
final String? notes;
|
|
final List<Attachment> attachments;
|
|
|
|
ScheduleMetadata({
|
|
this.color,
|
|
this.location,
|
|
this.notes,
|
|
this.attachments = const [],
|
|
});
|
|
}
|
|
|
|
enum AttachmentType { document, reminder }
|
|
|
|
class Attachment {
|
|
final String name;
|
|
final AttachmentType type;
|
|
final String? url;
|
|
final String? content;
|
|
final String? note;
|
|
|
|
Attachment({
|
|
required this.name,
|
|
required this.type,
|
|
this.url,
|
|
this.content,
|
|
this.note,
|
|
});
|
|
}
|
|
|
|
extension ScheduleSourceTypeExtension on ScheduleSourceType {
|
|
String get displayName {
|
|
switch (this) {
|
|
case ScheduleSourceType.manual:
|
|
return '手动创建';
|
|
case ScheduleSourceType.imported:
|
|
return '导入';
|
|
case ScheduleSourceType.agentGenerated:
|
|
return 'AI生成';
|
|
}
|
|
}
|
|
|
|
IconData get icon {
|
|
switch (this) {
|
|
case ScheduleSourceType.manual:
|
|
return Icons.edit_calendar;
|
|
case ScheduleSourceType.imported:
|
|
return Icons.download;
|
|
case ScheduleSourceType.agentGenerated:
|
|
return Icons.auto_awesome;
|
|
}
|
|
}
|
|
}
|