feat: 优化前端 UI 组件与交互体验

- 优化日历、待办、消息等页面交互
- 更新 ChatBloc 与 UI Schema 渲染
- 优化联系人、首页、设置页面体验
This commit is contained in:
qzl
2026-03-16 16:11:28 +08:00
parent a75c868bca
commit 4b92772535
18 changed files with 1591 additions and 1780 deletions
@@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_pressable.dart';
import '../calendar_state_manager.dart';
import '../calendar_time_utils.dart';
import '../widgets/bottom_dock.dart';
@@ -42,7 +43,6 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
late DateTime _selectedDate;
late List<DateTime> _monthDates;
final ScrollController _dayStripController = ScrollController();
Key _eventsKey = UniqueKey();
List<ScheduleItemModel> _events = const [];
@override
@@ -135,10 +135,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
right: AppSpacing.lg,
top: 2,
),
child: KeyedSubtree(
key: _eventsKey,
child: _buildTimelineBoard(),
),
child: RepaintBoundary(child: _buildTimelineBoard()),
),
),
),
@@ -223,13 +220,17 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
}
Widget _buildHeader() {
final monthLabel = '${_selectedDate.year}${_selectedDate.month}';
return SizedBox(
height: 68,
child: Padding(
padding: const EdgeInsets.only(left: 20, right: 20, top: 12, bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
AppPressable(
borderRadius: BorderRadius.circular(AppRadius.xl),
onTap: () => context.go('/calendar/month'),
child: Container(
height: 36,
@@ -241,6 +242,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
LucideIcons.chevronLeft,
@@ -248,12 +250,30 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
color: AppColors.slate700,
),
const SizedBox(width: 6),
Text(
'${_selectedDate.year}${_selectedDate.month}',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
AnimatedSwitcher(
duration: const Duration(milliseconds: 160),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeOut,
transitionBuilder: (child, animation) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.12),
end: Offset.zero,
).animate(animation),
child: child,
),
);
},
child: Text(
monthLabel,
key: ValueKey(monthLabel),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.slate700,
),
),
),
],
@@ -262,7 +282,8 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
),
const Spacer(),
if (!isSameDay(_selectedDate, DateTime.now()))
GestureDetector(
AppPressable(
borderRadius: BorderRadius.circular(AppRadius.xl),
onTap: _goToToday,
child: Container(
height: 36,
@@ -286,28 +307,24 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
),
if (!isSameDay(_selectedDate, DateTime.now()))
const SizedBox(width: 8),
GestureDetector(
AppPressable(
borderRadius: BorderRadius.circular(AppRadius.full),
onTap: () => CreateEventSheet.show(
context,
initialDate: _selectedDate,
onSaved: () {
setState(() {
_eventsKey = UniqueKey();
});
_loadEvents();
},
onSaved: _loadEvents,
),
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: AppColors.blue600,
borderRadius: BorderRadius.circular(18),
borderRadius: BorderRadius.circular(AppRadius.full),
),
child: const Icon(
LucideIcons.plus,
size: 20,
color: Colors.white,
color: AppColors.white,
),
),
),
@@ -318,35 +335,45 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
}
Widget _buildWeekStrip() {
final stripKey = ValueKey('${_selectedDate.year}-${_selectedDate.month}');
return SizedBox(
height: 86,
child: ListView.separated(
controller: _dayStripController,
scrollDirection: Axis.horizontal,
itemCount: _monthDates.length,
separatorBuilder: (context, index) =>
const SizedBox(width: _dayItemGap),
itemBuilder: (context, index) {
final date = _monthDates[index];
final isSelected = isSameDay(date, _selectedDate);
final isWeekend = date.weekday % 7 == 0 || date.weekday % 7 == 6;
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 180),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeOut,
child: ListView.separated(
key: stripKey,
controller: _dayStripController,
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: _monthDates.length,
separatorBuilder: (context, index) =>
const SizedBox(width: _dayItemGap),
itemBuilder: (context, index) {
final date = _monthDates[index];
final isSelected = isSameDay(date, _selectedDate);
final isWeekend = date.weekday % 7 == 0 || date.weekday % 7 == 6;
return GestureDetector(
onTap: () {
setState(() {
_selectedDate = date;
});
_calendarManager.setSelectedDate(date);
_updateMonthDates();
_scrollToSelectedDate(animate: true);
_loadEvents();
},
child: SizedBox(
width: _dayItemWidth,
child: _buildDayItem(date, isSelected, isWeekend),
),
);
},
return AppPressable(
borderRadius: BorderRadius.circular(AppRadius.xl),
onTap: () {
setState(() {
_selectedDate = date;
});
_calendarManager.setSelectedDate(date);
_updateMonthDates();
_scrollToSelectedDate(animate: true);
_loadEvents();
},
child: SizedBox(
width: _dayItemWidth,
child: _buildDayItem(date, isSelected, isWeekend),
),
);
},
),
),
);
}
@@ -389,6 +416,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
final dayNames = ['', '', '', '', '', '', ''];
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
@@ -399,14 +427,26 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
),
),
const SizedBox(height: 2),
Text(
'${date.day}',
style: TextStyle(
fontSize: 17,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
color: isSelected
? AppColors.blue600
: (isWeekend ? AppColors.slate400 : AppColors.slate900),
AnimatedContainer(
duration: const Duration(milliseconds: 140),
curve: Curves.easeOut,
width: 32,
height: 32,
decoration: BoxDecoration(
color: isSelected ? AppColors.blue100 : Colors.transparent,
borderRadius: BorderRadius.circular(AppRadius.full),
),
child: Center(
child: Text(
'${date.day}',
style: TextStyle(
fontSize: 17,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
color: isSelected
? AppColors.blue600
: (isWeekend ? AppColors.slate400 : AppColors.slate900),
),
),
),
),
],
@@ -480,6 +520,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
for (var i = 0; i < events.length; i++) {
final event = events[i];
final column = columns[i];
final eventColor = _parseColor(event.metadata?.color);
final startMinutes = event.startAt.hour * 60 + event.startAt.minute;
final endMinutes = event.endAt != null
@@ -507,22 +548,15 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
color: Colors.transparent,
child: InkWell(
onTap: () {
final path = '/calendar/events/${event.id}';
debugPrint('Navigating to: $path');
context.push(path);
context.push('/calendar/events/${event.id}');
},
child: Container(
margin: const EdgeInsets.only(right: 4),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: _parseColor(
event.metadata?.color,
).withValues(alpha: 0.2),
color: eventColor.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: _parseColor(event.metadata?.color),
width: 1,
),
border: Border.all(color: eventColor, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
@@ -531,7 +565,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
width: 6,
height: 6,
decoration: BoxDecoration(
color: _parseColor(event.metadata?.color),
color: eventColor,
shape: BoxShape.circle,
),
),
@@ -542,7 +576,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: _parseColor(event.metadata?.color),
color: eventColor,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,