fix: 修复日历事件详情页布局问题并添加底部输入框

- 修复 Row 中 Flexible 在无界宽度约束下的布局崩溃问题
- 用 Expanded 包裹内层 Row 提供有界宽度约束
- 添加底部输入框组件(+按钮、输入框、麦克风图标)
- 实现事件详情页动态数据展示
- 添加编辑和删除事件功能
- 添加事件不存在时的错误页面
This commit is contained in:
qzl
2026-03-02 17:28:21 +08:00
parent e161ca22c4
commit 6fb527eb7b
7 changed files with 1253 additions and 119 deletions
@@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
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 ScheduleMetadata? metadata;
final ScheduleSourceType sourceType;
final ScheduleStatus status;
final DateTime createdAt;
ScheduleItemModel({
required this.id,
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,
}) : createdAt = createdAt ?? DateTime.now();
ScheduleItemModel copyWith({
String? id,
String? title,
String? description,
DateTime? startAt,
DateTime? endAt,
String? timezone,
ScheduleMetadata? metadata,
ScheduleSourceType? sourceType,
ScheduleStatus? status,
DateTime? createdAt,
}) {
return ScheduleItemModel(
id: id ?? this.id,
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,
);
}
}
class ScheduleMetadata {
final String? color;
final String? location;
final String? notes;
final List<Attachment>? attachments;
ScheduleMetadata({this.color, this.location, this.notes, this.attachments});
ScheduleMetadata copyWith({
String? color,
String? location,
String? notes,
List<Attachment>? attachments,
}) {
return ScheduleMetadata(
color: color ?? this.color,
location: location ?? this.location,
notes: notes ?? this.notes,
attachments: attachments ?? this.attachments,
);
}
}
class Attachment {
final String name;
final String? url;
final String? content;
final String type;
Attachment({
required this.name,
this.url,
this.content,
this.type = 'document',
});
}
const defaultColors = [
Color(0xFF3B82F6),
Color(0xFF8B5CF6),
Color(0xFF10B981),
Color(0xFFF59E0B),
Color(0xFFEF4444),
];
@@ -0,0 +1,109 @@
import 'package:social_app/core/config/env.dart';
import '../models/schedule_item_model.dart';
class MockCalendarService {
static final MockCalendarService _instance = MockCalendarService._internal();
factory MockCalendarService() => _instance;
final List<ScheduleItemModel> _events = [];
MockCalendarService._internal();
List<ScheduleItemModel> get events => List.unmodifiable(_events);
List<ScheduleItemModel> getEventsForDay(DateTime date) {
final dateOnly = DateTime(date.year, date.month, date.day);
return _events.where((event) {
final eventDate = DateTime(
event.startAt.year,
event.startAt.month,
event.startAt.day,
);
return eventDate == dateOnly && event.status == ScheduleStatus.active;
}).toList()..sort((a, b) => a.startAt.compareTo(b.startAt));
}
List<ScheduleItemModel> getEventsForRange(DateTime start, DateTime end) {
return _events.where((event) {
return event.startAt.isAfter(start.subtract(const Duration(days: 1))) &&
event.startAt.isBefore(end.add(const Duration(days: 1))) &&
event.status == ScheduleStatus.active;
}).toList()..sort((a, b) => a.startAt.compareTo(b.startAt));
}
ScheduleItemModel? getEventById(String id) {
try {
return _events.firstWhere((e) => e.id == id);
} catch (_) {
return null;
}
}
void addEvent(ScheduleItemModel event) {
_events.add(event);
}
void updateEvent(ScheduleItemModel event) {
final index = _events.indexWhere((e) => e.id == event.id);
if (index >= 0) {
_events[index] = event;
}
}
void deleteEvent(String id) {
_events.removeWhere((e) => e.id == id);
}
}
class CalendarService {
static final CalendarService _instance = CalendarService._internal();
factory CalendarService() => _instance;
CalendarService._internal();
MockCalendarService get _mock => MockCalendarService();
List<ScheduleItemModel> getEventsForDay(DateTime date) {
if (Env.isMockApi) {
return _mock.getEventsForDay(date);
}
throw UnimplementedError('Real API not implemented');
}
List<ScheduleItemModel> getEventsForRange(DateTime start, DateTime end) {
if (Env.isMockApi) {
return _mock.getEventsForRange(start, end);
}
throw UnimplementedError('Real API not implemented');
}
ScheduleItemModel? getEventById(String id) {
if (Env.isMockApi) {
return _mock.getEventById(id);
}
throw UnimplementedError('Real API not implemented');
}
void addEvent(ScheduleItemModel event) {
if (Env.isMockApi) {
_mock.addEvent(event);
return;
}
throw UnimplementedError('Real API not implemented');
}
void updateEvent(ScheduleItemModel event) {
if (Env.isMockApi) {
_mock.updateEvent(event);
return;
}
throw UnimplementedError('Real API not implemented');
}
void deleteEvent(String id) {
if (Env.isMockApi) {
_mock.deleteEvent(id);
return;
}
throw UnimplementedError('Real API not implemented');
}
}