refactor: 重构 Agent 模块为 AgentScope,删除旧版 CrewAI/LiteLLM 实现

This commit is contained in:
qzl
2026-03-11 20:51:56 +08:00
parent 177ed616bf
commit 145e3dc615
149 changed files with 5120 additions and 11356 deletions
@@ -113,7 +113,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (!didPop) {
context.go('/calendar/month?date=${formatYmd(_selectedDate)}');
context.go('/home');
}
},
child: SafeArea(
@@ -7,6 +7,7 @@ import '../../../../core/theme/design_tokens.dart';
import '../../data/services/mock_calendar_service.dart';
import '../../data/models/schedule_item_model.dart';
import '../widgets/create_event_sheet.dart';
import '../widgets/calendar_share_dialog.dart';
class CalendarEventDetailScreen extends StatefulWidget {
final String eventId;
@@ -239,49 +240,77 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
),
Row(
children: [
GestureDetector(
onTap: () => CreateEventSheet.edit(
context,
event,
onSaved: () {
setState(() {
_loadEvent();
});
},
),
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFF8FAFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFDCE5F4)),
if (event.canEdit)
GestureDetector(
onTap: () => CreateEventSheet.edit(
context,
event,
onSaved: () {
setState(() {
_loadEvent();
});
},
),
child: const Icon(
LucideIcons.pencil,
size: 18,
color: AppColors.slate600,
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFF8FAFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFDCE5F4)),
),
child: const Icon(
LucideIcons.pencil,
size: 18,
color: AppColors.slate600,
),
),
),
),
const SizedBox(width: 8),
GestureDetector(
onTap: _showDeleteConfirmation,
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFFFF1F2),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFFECACA)),
),
child: const Icon(
LucideIcons.trash2,
size: 18,
color: AppColors.red500,
if (event.canEdit) const SizedBox(width: 8),
if (event.canDelete)
GestureDetector(
onTap: _showDeleteConfirmation,
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFFFF1F2),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFFECACA)),
),
child: const Icon(
LucideIcons.trash2,
size: 18,
color: AppColors.red500,
),
),
),
),
if (event.canInvite) ...[
const SizedBox(width: 8),
GestureDetector(
onTap: () => CalendarShareDialog.show(
context,
event.id,
event.title,
canInvite: event.canInvite,
canEdit: event.canEdit,
),
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFF0FDF4),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFBBF7D0)),
),
child: const Icon(
LucideIcons.share2,
size: 18,
color: AppColors.slate600,
),
),
),
],
],
),
],
@@ -302,9 +331,11 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
TextButton(
onPressed: () async {
await sl<CalendarService>().deleteEvent(widget.eventId);
await sl<LocalNotificationService>().cancelEventReminder(
widget.eventId,
);
try {
await sl<LocalNotificationService>().cancelEventReminder(
widget.eventId,
);
} catch (_) {}
if (!context.mounted) {
return;
}
@@ -0,0 +1,207 @@
import 'package:flutter/material.dart' hide BackButton;
import '../../../../core/di/injection.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import '../../data/calendar_api.dart';
class CalendarShareDialog extends StatefulWidget {
final String eventId;
final String eventTitle;
final bool canInvite;
final bool canEdit;
const CalendarShareDialog({
super.key,
required this.eventId,
required this.eventTitle,
this.canInvite = false,
this.canEdit = false,
});
static Future<void> show(
BuildContext context,
String eventId,
String eventTitle, {
bool canInvite = false,
bool canEdit = false,
}) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => CalendarShareDialog(
eventId: eventId,
eventTitle: eventTitle,
canInvite: canInvite,
canEdit: canEdit,
),
);
}
@override
State<CalendarShareDialog> createState() => _CalendarShareDialogState();
}
class _CalendarShareDialogState extends State<CalendarShareDialog> {
final _emailController = TextEditingController();
bool _permissionView = true;
bool _permissionEdit = false;
bool _permissionInvite = false;
bool _isLoading = false;
@override
void dispose() {
_emailController.dispose();
super.dispose();
}
Future<void> _handleShare() async {
final email = _emailController.text.trim();
if (email.isEmpty) {
Toast.show(context, '请输入邮箱地址', type: ToastType.error);
return;
}
setState(() => _isLoading = true);
try {
final api = sl<CalendarApi>();
await api.share(
widget.eventId,
email: email,
view: _permissionView,
edit: _permissionEdit,
invite: _permissionInvite,
);
if (mounted) {
Toast.show(context, '邀请已发送', type: ToastType.success);
Navigator.of(context).pop();
}
} catch (e) {
if (mounted) {
Toast.show(context, '发送邀请失败', type: ToastType.error);
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
decoration: BoxDecoration(
color: AppColors.background,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppRadius.lg),
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'分享日历',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close),
),
],
),
const SizedBox(height: AppSpacing.md),
Text(widget.eventTitle, style: const TextStyle(fontSize: 16)),
const SizedBox(height: AppSpacing.lg),
TextField(
controller: _emailController,
decoration: InputDecoration(
labelText: '邮箱地址',
hintText: '输入对方的邮箱',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.md),
),
),
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: AppSpacing.lg),
const Text('权限设置', style: TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: AppSpacing.sm),
_buildPermissionSwitch('查看', '可以查看此日历事件(必选)', true, null),
_buildPermissionSwitch(
'编辑',
'可以编辑此日历事件',
_permissionEdit,
widget.canEdit
? (v) => setState(() => _permissionEdit = v)
: null,
),
_buildPermissionSwitch(
'邀请',
'可以邀请其他人',
_permissionInvite,
widget.canInvite
? (v) => setState(() => _permissionInvite = v)
: null,
),
const SizedBox(height: AppSpacing.lg),
AppButton(
text: '发送邀请',
onPressed: _isLoading ? null : _handleShare,
isLoading: _isLoading,
),
],
),
),
),
);
}
Widget _buildPermissionSwitch(
String title,
String description,
bool value,
ValueChanged<bool>? onChanged,
) {
final enabled = onChanged != null;
return Padding(
padding: const EdgeInsets.symmetric(vertical: AppSpacing.xs),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(color: enabled ? null : Colors.grey),
),
Text(
description,
style: TextStyle(
fontSize: 12,
color: enabled ? Colors.grey : Colors.grey.shade400,
),
),
],
),
),
Switch(value: value, onChanged: enabled ? onChanged : null),
],
),
);
}
}
@@ -98,8 +98,6 @@ class _CreateEventSheetState extends State<CreateEventSheet>
_endDate = now;
_endTime = now.add(const Duration(hours: 1));
}
_titleController.addListener(() => setState(() {}));
}
@override
@@ -163,18 +161,23 @@ class _CreateEventSheetState extends State<CreateEventSheet>
color: AppColors.slate900,
),
),
GestureDetector(
onTap: _saveEvent,
child: Text(
'保存',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: _titleController.text.trim().isNotEmpty
? AppColors.blue600
: AppColors.slate400,
),
),
ValueListenableBuilder<TextEditingValue>(
valueListenable: _titleController,
builder: (context, value, child) {
return GestureDetector(
onTap: _saveEvent,
child: Text(
'保存',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: value.text.trim().isNotEmpty
? AppColors.blue600
: AppColors.slate400,
),
),
);
},
),
],
),
@@ -233,9 +236,10 @@ class _CreateEventSheetState extends State<CreateEventSheet>
time.hour,
time.minute,
);
if (endDateTime.isBefore(startDateTime)) {
if (endDateTime.isBefore(startDateTime) ||
endDateTime.isAtSameMomentAs(startDateTime)) {
_endDate = date;
_endTime = time;
_endTime = time.add(const Duration(hours: 1));
}
}
});
@@ -261,9 +265,10 @@ class _CreateEventSheetState extends State<CreateEventSheet>
time.hour,
time.minute,
);
if (endDateTime.isBefore(startDateTime)) {
if (endDateTime.isBefore(startDateTime) ||
endDateTime.isAtSameMomentAs(startDateTime)) {
_endDate = _startDate;
_endTime = _startTime;
_endTime = _startTime.add(const Duration(hours: 1));
} else {
_endDate = date;
_endTime = time;
@@ -271,6 +276,13 @@ class _CreateEventSheetState extends State<CreateEventSheet>
});
},
isOptional: true,
minTime: DateTime(
_startDate.year,
_startDate.month,
_startDate.day,
_startTime.hour,
_startTime.minute,
),
),
],
),
@@ -620,6 +632,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
DateTime time,
Function(DateTime, DateTime) onChanged, {
bool isOptional = false,
DateTime? minTime,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -635,7 +648,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
const SizedBox(height: 8),
InkWell(
onTap: () async {
final picked = await _pickDateTime(date, time);
final picked = await _pickDateTime(date, time, minTime: minTime);
if (picked == null) {
return;
}
@@ -687,14 +700,18 @@ class _CreateEventSheetState extends State<CreateEventSheet>
Future<(DateTime, DateTime)?> _pickDateTime(
DateTime date,
DateTime time,
) async {
DateTime time, {
DateTime? minTime,
}) async {
final result = await showModalBottomSheet<(DateTime, DateTime)>(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (context) =>
_DateTimePickerSheet(initialDate: date, initialTime: time),
builder: (context) => _DateTimePickerSheet(
initialDate: date,
initialTime: time,
minTime: minTime,
),
);
return result;
}
@@ -845,6 +862,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
id: _isEditing
? widget.editingEvent!.id
: 'evt_${DateTime.now().millisecondsSinceEpoch}',
ownerId: widget.editingEvent?.ownerId ?? '',
title: _titleController.text.trim(),
description: _descriptionController.text.trim().isNotEmpty
? _descriptionController.text.trim()
@@ -856,7 +874,6 @@ class _CreateEventSheetState extends State<CreateEventSheet>
try {
final service = sl<CalendarService>();
final notificationService = sl<LocalNotificationService>();
late final ScheduleItemModel saved;
if (_isEditing) {
@@ -864,7 +881,11 @@ class _CreateEventSheetState extends State<CreateEventSheet>
} else {
saved = await service.addEvent(event);
}
await notificationService.upsertEventReminder(saved);
try {
final notificationService = sl<LocalNotificationService>();
await notificationService.upsertEventReminder(saved);
} catch (_) {}
widget.onSaved?.call();
if (mounted) {
@@ -889,10 +910,12 @@ class _CreateEventSheetState extends State<CreateEventSheet>
class _DateTimePickerSheet extends StatefulWidget {
final DateTime initialDate;
final DateTime initialTime;
final DateTime? minTime;
const _DateTimePickerSheet({
required this.initialDate,
required this.initialTime,
this.minTime,
});
@override
@@ -915,10 +938,49 @@ class _DateTimePickerSheetState extends State<_DateTimePickerSheet> {
static final int _baseYear = DateTime.now().year;
static final List<int> _years = List.generate(21, (i) => _baseYear - 10 + i);
static final List<int> _months = List.generate(12, (i) => i + 1);
static final List<int> _hours = List.generate(24, (i) => i);
static final List<int> _minutes = List.generate(60, (i) => i);
static final List<int> _allHours = List.generate(24, (i) => i);
static final List<int> _allMinutes = List.generate(60, (i) => i);
List<int> _days = [];
late List<int> _filteredHours;
late List<int> _filteredMinutes;
List<int> _getFilteredHours() {
if (widget.minTime == null) return _allHours;
final minDate = widget.minTime!;
if (_selectedYear > minDate.year ||
(_selectedYear == minDate.year && _selectedMonth > minDate.month) ||
(_selectedYear == minDate.year &&
_selectedMonth == minDate.month &&
_selectedDay > minDate.day)) {
return _allHours;
}
if (_selectedYear == minDate.year &&
_selectedMonth == minDate.month &&
_selectedDay == minDate.day) {
return _allHours.where((h) => h > minDate.hour).toList();
}
return _allHours;
}
List<int> _getFilteredMinutes() {
if (widget.minTime == null) return _allMinutes;
final minDate = widget.minTime!;
if (_selectedYear > minDate.year ||
(_selectedYear == minDate.year && _selectedMonth > minDate.month) ||
(_selectedYear == minDate.year &&
_selectedMonth == minDate.month &&
_selectedDay > minDate.day)) {
return _allMinutes;
}
if (_selectedYear == minDate.year &&
_selectedMonth == minDate.month &&
_selectedDay == minDate.day &&
_selectedHour == minDate.hour) {
return _allMinutes.where((m) => m > minDate.minute).toList();
}
return _allMinutes;
}
@override
void initState() {
@@ -928,6 +990,8 @@ class _DateTimePickerSheetState extends State<_DateTimePickerSheet> {
_selectedDay = widget.initialDate.day;
_selectedHour = widget.initialTime.hour;
_selectedMinute = widget.initialTime.minute;
_filteredHours = _getFilteredHours();
_filteredMinutes = _getFilteredMinutes();
_updateDays();
_yearController = FixedExtentScrollController(
@@ -937,9 +1001,11 @@ class _DateTimePickerSheetState extends State<_DateTimePickerSheet> {
initialItem: _selectedMonth - 1,
);
_dayController = FixedExtentScrollController(initialItem: _selectedDay - 1);
_hourController = FixedExtentScrollController(initialItem: _selectedHour);
_hourController = FixedExtentScrollController(
initialItem: _filteredHours.indexOf(_selectedHour),
);
_minuteController = FixedExtentScrollController(
initialItem: _selectedMinute,
initialItem: _filteredMinutes.indexOf(_selectedMinute),
);
}
@@ -1055,9 +1121,26 @@ class _DateTimePickerSheetState extends State<_DateTimePickerSheet> {
children: [
Expanded(
child: _buildPicker(
_hours,
_filteredHours,
_hourController,
(v) => setState(() => _selectedHour = v),
(v) {
setState(() {
_selectedHour = v;
_filteredMinutes = _getFilteredMinutes();
if (_selectedMinute >
_filteredMinutes.last) {
_selectedMinute =
_filteredMinutes.isNotEmpty
? _filteredMinutes.last
: 0;
_minuteController.jumpToItem(
_filteredMinutes.indexOf(
_selectedMinute,
),
);
}
});
},
(v) => v.toString().padLeft(2, '0'),
itemExtent: 50,
),
@@ -1072,7 +1155,7 @@ class _DateTimePickerSheetState extends State<_DateTimePickerSheet> {
),
Expanded(
child: _buildPicker(
_minutes,
_filteredMinutes,
_minuteController,
(v) => setState(() => _selectedMinute = v),
(v) => v.toString().padLeft(2, '0'),