diff --git a/apps/lib/core/theme/design_tokens.dart b/apps/lib/core/theme/design_tokens.dart index 04c937a..2944509 100644 --- a/apps/lib/core/theme/design_tokens.dart +++ b/apps/lib/core/theme/design_tokens.dart @@ -61,6 +61,33 @@ class AppColors { static const appIconRing = Color(0xFFE8F3FF); static const appIconBorder = Color(0xFFC7DDFB); static const appTitle = Color(0xFF1E293B); + + static const todoBg = Color(0xFFF8FAFC); + static const todoCardBg = Color(0xFFFFFFFF); + + static const g1Text = Color(0xFFB91C1C); + static const g1Divider = Color(0xFFFEE2E2); + static const g1Border = Color(0xFFF3C6C6); + + static const g2Text = Color(0xFFB45309); + static const g2Divider = Color(0xFFFFEDD5); + static const g2Border = Color(0xFFFDE2B8); + + static const g3Text = Color(0xFF1D4ED8); + static const g3Divider = Color(0xFFEAF3FF); + static const g3Border = Color(0xFFCFE1FB); + + static const todoDetailCardBorder = Color(0xFFDCE5F4); + static const todoEventBorder1 = Color(0xFFDCE5F4); + static const todoEventBorder2 = Color(0xFFDCC8FF); + static const todoEventBorder3 = Color(0xFFCFE1FB); + + static const todoToggleBg = Color(0xFFFDFEFF); + static const todoToggleBorder = Color(0xFFDCE6F4); + static const todoToggleActiveBg = Color(0xFFD6E6FF); + static const todoToggleActiveBorder = Color(0xFFBFD6FB); + static const todoHomeBtnBg = Color(0xFFE6EEFB); + static const todoHomeBtnBorder = Color(0xFFC9D8EE); } class AppSpacing { diff --git a/apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart b/apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart new file mode 100644 index 0000000..6b4eb47 --- /dev/null +++ b/apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart @@ -0,0 +1,296 @@ +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import '../../../../core/theme/design_tokens.dart'; +import '../widgets/bottom_dock.dart'; + +class CalendarDayWeekScreen extends StatefulWidget { + const CalendarDayWeekScreen({super.key}); + + @override + State createState() => _CalendarDayWeekScreenState(); +} + +class _CalendarDayWeekScreenState extends State { + DateTime _selectedDate = DateTime(2026, 2, 9); + late DateTime _weekStart; + + @override + void initState() { + super.initState(); + _weekStart = _getWeekStart(_selectedDate); + } + + DateTime _getWeekStart(DateTime date) { + return date.subtract(Duration(days: date.weekday % 7)); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFF8FAFC), + body: SafeArea( + child: Column( + children: [ + _buildHeader(), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 2, + bottom: 104, + ), + child: Column( + children: [ + _buildWeekStrip(), + const SizedBox(height: 8), + _buildTimelineBoard(), + ], + ), + ), + ), + ), + _buildBottomDock(), + ], + ), + ), + ); + } + + Widget _buildHeader() { + return SizedBox( + height: 68, + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 20, top: 12, bottom: 8), + child: Row( + children: [ + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + height: 36, + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: const Color(0xFFF8FAFF), + borderRadius: BorderRadius.circular(18), + border: Border.all(color: const Color(0xFFDEE7F6)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + LucideIcons.chevronLeft, + size: 16, + color: AppColors.slate700, + ), + const SizedBox(width: 6), + Text( + '${_selectedDate.year}年${_selectedDate.month}月', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.slate700, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildWeekStrip() { + return SizedBox( + height: 86, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(7, (index) { + final date = _weekStart.add(Duration(days: index)); + final isSelected = + date.day == _selectedDate.day && + date.month == _selectedDate.month && + date.year == _selectedDate.year; + final isWeekend = index == 0 || index == 6; + + return GestureDetector( + onTap: () { + setState(() { + _selectedDate = date; + }); + }, + child: _buildDayItem(date, isSelected, isWeekend), + ); + }), + ), + ); + } + + Widget _buildDayItem(DateTime date, bool isSelected, bool isWeekend) { + final dayNames = ['日', '一', '二', '三', '四', '五', '六']; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + dayNames[date.weekday % 7], + style: TextStyle( + fontSize: 11, + color: isWeekend ? AppColors.slate400 : AppColors.slate600, + ), + ), + const SizedBox(height: 2), + Text( + '${date.day}', + style: TextStyle( + fontSize: isSelected ? 17 : (isWeekend ? 17 : 17), + fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600, + color: isSelected + ? AppColors.blue600 + : (isWeekend ? AppColors.slate400 : AppColors.slate900), + ), + ), + ], + ); + } + + Widget _buildTimelineBoard() { + return Column( + children: [ + _buildTimelineRow('07:00', false), + _buildTimelineRow('08:00', false), + _buildTimelineRow( + '09:00', + true, + eventText: '购票提醒', + eventColor: AppColors.slate500, + ), + _buildTimelineRow('10:00', false), + _buildTimelineRow('11:00', false), + _buildTimelineRow('12:00', false), + _buildTimelineRow('13:00', false), + _buildTimelineRow('14:00', false), + _buildTimelineRow('15:00', false), + _buildTimelineRow('15:28', false, isCurrentTime: true), + _buildTimelineRow( + '16:00', + true, + eventText: '购票提醒', + eventColor: const Color(0xFF6B21A8), + eventBg: const Color(0xFFE9D5FF), + eventBorder: const Color(0xFFD8B4FE), + ), + _buildTimelineRow('17:00', false), + _buildTimelineRow('18:00', false), + _buildTimelineRow('19:00', false), + _buildTimelineRow('20:00', false), + _buildTimelineRow('21:00', false), + _buildTimelineRow('22:00', false), + _buildTimelineRow('00:00', false, isDisabled: true), + ], + ); + } + + Widget _buildTimelineRow( + String time, + bool hasEvent, { + String? eventText, + Color? eventColor, + Color? eventBg, + Color? eventBorder, + bool isCurrentTime = false, + bool isDisabled = false, + }) { + return SizedBox( + height: 34, + child: Row( + children: [ + SizedBox( + width: 44, + child: isCurrentTime + ? Container( + width: 44, + height: 18, + decoration: BoxDecoration( + color: const Color(0xFFEF4444), + borderRadius: BorderRadius.circular(9), + ), + child: Center( + child: Text( + time, + style: const TextStyle( + fontSize: 10, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + ), + ) + : Text( + time, + textAlign: TextAlign.right, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: isDisabled + ? AppColors.slate300 + : const Color(0xFF9CA3AF), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: isCurrentTime + ? Container( + height: 2, + decoration: BoxDecoration( + color: const Color(0xFFEF4444), + borderRadius: BorderRadius.circular(99), + ), + ) + : hasEvent + ? Container( + height: 22, + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: eventBg ?? const Color(0xFFE5E7EB), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: eventBorder ?? const Color(0xFFD1D5DB), + ), + ), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + eventText ?? '', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: eventColor ?? const Color(0xFF6B7280), + ), + ), + ), + ) + : Container( + height: 1, + color: isDisabled + ? const Color(0xFFECEFF4) + : const Color(0xFFE5E7EB), + ), + ), + ], + ), + ); + } + + Widget _buildBottomDock() { + return BottomDock( + activeTab: DockTab.calendar, + onTodoTap: () {}, + onCalendarTap: () {}, + onHomeTap: () => Navigator.of(context).pop(), + ); + } +} diff --git a/apps/lib/features/calendar/ui/screens/calendar_month_screen.dart b/apps/lib/features/calendar/ui/screens/calendar_month_screen.dart new file mode 100644 index 0000000..5dbc3db --- /dev/null +++ b/apps/lib/features/calendar/ui/screens/calendar_month_screen.dart @@ -0,0 +1,357 @@ +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import '../../../../core/theme/design_tokens.dart'; +import '../widgets/bottom_dock.dart'; + +class CalendarMonthScreen extends StatefulWidget { + const CalendarMonthScreen({super.key}); + + @override + State createState() => _CalendarMonthScreenState(); +} + +class _CalendarMonthScreenState extends State { + DateTime _currentMonth = DateTime(2026, 2, 1); + DateTime? _selectedDate; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFF8FAFC), + body: SafeArea( + child: Column( + children: [ + _buildHeader(), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(bottom: 84), + child: _buildMonthContent(), + ), + ), + ), + _buildBottomDock(), + ], + ), + ), + ); + } + + Widget _buildHeader() { + return SizedBox( + height: 76, + child: Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 14, bottom: 6), + child: Column( + children: [ + SizedBox( + height: 56, + child: Row( + children: [ + GestureDetector( + onTap: () => _showMonthPicker(), + child: Row( + children: [ + Text( + '${_currentMonth.month}月', + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.w700, + color: AppColors.slate900, + ), + ), + const SizedBox(width: 6), + const Icon( + LucideIcons.chevronDown, + size: 16, + color: AppColors.slate900, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildMonthContent() { + return Column( + children: [ + _buildWeekdayHeader(), + Container(height: 1, color: const Color(0xFFE5E7EB)), + ..._buildWeeks(), + ], + ); + } + + Widget _buildWeekdayHeader() { + const weekdays = ['日', '一', '二', '三', '四', '五', '六']; + return SizedBox( + height: 40, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: weekdays + .map( + (day) => SizedBox( + width: 36, + child: Center( + child: Text( + day, + style: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: Color(0xFF9CA3AF), + ), + ), + ), + ), + ) + .toList(), + ), + ), + ); + } + + List _buildWeeks() { + final firstDayOfMonth = DateTime( + _currentMonth.year, + _currentMonth.month, + 1, + ); + final lastDayOfMonth = DateTime( + _currentMonth.year, + _currentMonth.month + 1, + 0, + ); + final startWeekday = firstDayOfMonth.weekday % 7; + + final daysInMonth = lastDayOfMonth.day; + final totalCells = ((daysInMonth + startWeekday) / 7).ceil() * 7; + + final weeks = []; + for (var weekStart = 0; weekStart < totalCells; weekStart += 7) { + weeks.add(_buildWeekRow(weekStart, startWeekday, daysInMonth)); + if (weekStart + 7 < totalCells) { + weeks.add(Container(height: 1, color: const Color(0xFFE5E7EB))); + } + } + + return weeks; + } + + Widget _buildWeekRow(int weekStart, int startWeekday, int daysInMonth) { + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 14, bottom: 0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(7, (index) { + final dayIndex = weekStart + index - startWeekday + 1; + if (dayIndex < 1 || dayIndex > daysInMonth) { + return SizedBox(width: 36, height: 36); + } + + final date = DateTime( + _currentMonth.year, + _currentMonth.month, + dayIndex, + ); + final isSelected = + _selectedDate != null && + _selectedDate!.day == dayIndex && + _selectedDate!.month == _currentMonth.month; + + return GestureDetector( + onTap: () { + setState(() { + _selectedDate = date; + }); + }, + child: Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: isSelected + ? const Color(0xFFDBEAFE) + : Colors.transparent, + borderRadius: BorderRadius.circular(18), + ), + child: Center( + child: Text( + '$dayIndex', + style: TextStyle( + fontSize: 15, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.normal, + color: isSelected + ? AppColors.blue600 + : AppColors.slate900, + ), + ), + ), + ), + ); + }), + ), + const SizedBox(height: 10), + _buildWeekEvents(weekStart, startWeekday, daysInMonth), + ], + ), + ); + } + + Widget _buildWeekEvents(int weekStart, int startWeekday, int daysInMonth) { + return SizedBox( + height: 70, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(7, (index) { + final dayIndex = weekStart + index - startWeekday + 1; + if (dayIndex == 10) { + return _buildEventDot(); + } + return const SizedBox(width: 38, height: 1); + }), + ), + ); + } + + Widget _buildEventDot() { + return SizedBox( + width: 76, + height: 100, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 20, + padding: const EdgeInsets.symmetric(horizontal: 6), + decoration: BoxDecoration( + color: const Color(0xFFE5E7EB), + borderRadius: BorderRadius.circular(6), + ), + child: const Center( + child: Text( + '购票提醒', + style: TextStyle( + fontSize: 9, + fontWeight: FontWeight.w500, + color: Color(0xFF6B7280), + ), + ), + ), + ), + const SizedBox(height: 4), + Container( + height: 20, + padding: const EdgeInsets.symmetric(horizontal: 6), + decoration: BoxDecoration( + color: const Color(0xFFE9D5FF), + borderRadius: BorderRadius.circular(6), + ), + child: const Center( + child: Text( + '购票提醒', + style: TextStyle( + fontSize: 9, + fontWeight: FontWeight.w500, + color: Color(0xFF6B21A8), + ), + ), + ), + ), + ], + ), + ); + } + + void _showMonthPicker() { + showModalBottomSheet( + context: context, + builder: (context) => Container( + height: 300, + color: Colors.white, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('取消'), + ), + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('确定'), + ), + ], + ), + Expanded( + child: Row( + children: [ + Expanded( + child: CupertinoPicker( + itemExtent: 40, + scrollController: FixedExtentScrollController( + initialItem: _currentMonth.year - 2020, + ), + onSelectedItemChanged: (index) { + setState(() { + _currentMonth = DateTime( + 2020 + index, + _currentMonth.month, + 1, + ); + }); + }, + children: List.generate(20, (index) { + return Center(child: Text('${2020 + index}年')); + }), + ), + ), + Expanded( + child: CupertinoPicker( + itemExtent: 40, + scrollController: FixedExtentScrollController( + initialItem: _currentMonth.month - 1, + ), + onSelectedItemChanged: (index) { + setState(() { + _currentMonth = DateTime( + _currentMonth.year, + index + 1, + 1, + ); + }); + }, + children: List.generate(12, (index) { + return Center(child: Text('${index + 1}月')); + }), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildBottomDock() { + return BottomDock( + activeTab: DockTab.calendar, + onTodoTap: () {}, + onCalendarTap: () {}, + onHomeTap: () => Navigator.of(context).pop(), + ); + } +} diff --git a/apps/lib/features/calendar/ui/widgets/bottom_dock.dart b/apps/lib/features/calendar/ui/widgets/bottom_dock.dart new file mode 100644 index 0000000..3844a8f --- /dev/null +++ b/apps/lib/features/calendar/ui/widgets/bottom_dock.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import '../../../../core/theme/design_tokens.dart'; + +enum DockTab { todo, calendar } + +class BottomDock extends StatelessWidget { + final DockTab activeTab; + final VoidCallback? onTodoTap; + final VoidCallback? onCalendarTap; + final VoidCallback? onHomeTap; + + const BottomDock({ + super.key, + required this.activeTab, + this.onTodoTap, + this.onCalendarTap, + this.onHomeTap, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: 61, + padding: const EdgeInsets.only(left: 20, right: 20, top: 12, bottom: 18), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [_buildToggle(), _buildHomeBtn()], + ), + ); + } + + Widget _buildToggle() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFFFDFEFF), + borderRadius: BorderRadius.circular(24), + border: Border.all(color: const Color(0xFFDCE6F4)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildToggleItem( + icon: LucideIcons.listTodo, + isActive: activeTab == DockTab.todo, + onTap: onTodoTap, + ), + const SizedBox(width: 4), + _buildToggleItem( + icon: LucideIcons.calendar, + isActive: activeTab == DockTab.calendar, + onTap: onCalendarTap, + ), + ], + ), + ); + } + + Widget _buildToggleItem({ + required IconData icon, + required bool isActive, + VoidCallback? onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: isActive ? const Color(0xFFD6E6FF) : Colors.transparent, + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: isActive ? const Color(0xFFBFD6FB) : Colors.transparent, + ), + ), + child: Icon( + icon, + size: 20, + color: isActive ? const Color(0xFF1D4ED8) : const Color(0xFF334155), + ), + ), + ); + } + + Widget _buildHomeBtn() { + return GestureDetector( + onTap: onHomeTap, + child: Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: const Color(0xFFE6EEFB), + borderRadius: BorderRadius.circular(18), + border: Border.all(color: const Color(0xFFC9D8EE)), + ), + child: const Icon( + LucideIcons.house, + size: 20, + color: Color(0xFF1E3A8A), + ), + ), + ); + } +} diff --git a/apps/lib/features/todo/ui/screens/todo_detail_screen.dart b/apps/lib/features/todo/ui/screens/todo_detail_screen.dart new file mode 100644 index 0000000..9bd56f0 --- /dev/null +++ b/apps/lib/features/todo/ui/screens/todo_detail_screen.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import '../../../../core/theme/design_tokens.dart'; + +class TodoDetailScreen extends StatelessWidget { + const TodoDetailScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.todoBg, + body: SafeArea( + child: Column( + children: [ + _buildHeader(context), + Expanded(child: _buildContent()), + ], + ), + ), + ); + } + + Widget _buildHeader(BuildContext context) { + return SizedBox( + height: 64, + child: Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 12, bottom: 8), + child: Align( + alignment: Alignment.centerLeft, + child: GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: AppColors.messageBtnWrap, + borderRadius: BorderRadius.circular(18), + border: Border.all(color: AppColors.messageBtnBorder, width: 1), + ), + child: const Icon( + LucideIcons.chevronLeft, + size: 16, + color: AppColors.slate700, + ), + ), + ), + ), + ), + ); + } + + Widget _buildContent() { + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 20), + child: ListView( + children: [ + _buildMainCard(), + const SizedBox(height: 12), + const Text( + '日历事件卡片', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.slate500, + ), + ), + const SizedBox(height: 10), + _buildEventCard( + title: '完成活动海报设计', + time: '2026年2月9日 09:00 - 09:30', + borderColor: AppColors.todoEventBorder1, + ), + const SizedBox(height: 10), + _buildEventCard( + title: '活动方案评审会议', + time: '2026年2月9日 16:00 - 17:00', + borderColor: AppColors.todoEventBorder2, + ), + const SizedBox(height: 10), + _buildEventCard( + title: '提交最终活动方案', + time: '2026年2月10日 10:00 - 10:20', + borderColor: AppColors.todoEventBorder3, + ), + ], + ), + ); + } + + Widget _buildMainCard() { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.todoCardBg, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: AppColors.todoDetailCardBorder, width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '活动发布准备', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 18, + fontWeight: FontWeight.w700, + color: AppColors.slate900, + ), + ), + const SizedBox(height: 4), + const Text( + '截止今天 18:00 · 已拆分为多个日历事件', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.slate500, + ), + ), + const SizedBox(height: 8), + Container(height: 1, color: const Color(0xFFE5E7EB)), + const SizedBox(height: 8), + _buildInfoRow( + label: '所属象限', + value: '重要紧急', + valueColor: AppColors.g1Text, + ), + const SizedBox(height: 8), + _buildInfoRow( + label: '关联日历事件', + value: '3个', + valueColor: AppColors.g3Text, + ), + ], + ), + ); + } + + Widget _buildInfoRow({ + required String label, + required String value, + required Color valueColor, + }) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColors.slate400, + ), + ), + Text( + value, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 12, + fontWeight: FontWeight.w700, + color: valueColor, + ), + ), + ], + ); + } + + Widget _buildEventCard({ + required String title, + required String time, + required Color borderColor, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.todoCardBg, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: borderColor, width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 13, + fontWeight: FontWeight.w600, + color: AppColors.slate700, + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: AppColors.slate300, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: AppColors.slate300, + shape: BoxShape.circle, + ), + ), + ], + ), + ], + ), + const SizedBox(height: 8), + Text( + time, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.slate500, + ), + ), + ], + ), + ); + } +} diff --git a/apps/lib/features/todo/ui/screens/todo_quadrants_screen.dart b/apps/lib/features/todo/ui/screens/todo_quadrants_screen.dart new file mode 100644 index 0000000..354f5c7 --- /dev/null +++ b/apps/lib/features/todo/ui/screens/todo_quadrants_screen.dart @@ -0,0 +1,254 @@ +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import '../../../../core/theme/design_tokens.dart'; + +class TodoQuadrantsScreen extends StatelessWidget { + const TodoQuadrantsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.todoBg, + body: SafeArea( + child: Column( + children: [ + _buildHeader(), + Expanded(child: _buildContent()), + _buildBottomDock(), + ], + ), + ), + ); + } + + Widget _buildHeader() { + return SizedBox( + height: 72, + child: Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 14, bottom: 8), + child: Align( + alignment: Alignment.centerLeft, + child: const Text( + '待办事项', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 22, + fontWeight: FontWeight.w700, + color: AppColors.slate900, + ), + ), + ), + ), + ); + } + + Widget _buildContent() { + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 96), + child: ListView( + children: [ + _buildQuadrant( + title: '重要紧急', + count: 2, + textColor: AppColors.g1Text, + dividerColor: AppColors.g1Divider, + borderColor: AppColors.g1Border, + items: ['18:00 前提交活动方案', '回复客户邀约确认'], + ), + const SizedBox(height: 12), + _buildQuadrant( + title: '紧急不重要', + count: 2, + textColor: AppColors.g2Text, + dividerColor: AppColors.g2Divider, + borderColor: AppColors.g2Border, + items: ['确认会场停车信息', '代订明早高铁票'], + ), + const SizedBox(height: 12), + _buildQuadrant( + title: '重要不紧急', + count: 3, + textColor: AppColors.g3Text, + dividerColor: AppColors.g3Divider, + borderColor: AppColors.g3Border, + items: ['本周复盘与下周规划', '整理个人知识库结构', '优化三月目标里程碑'], + ), + ], + ), + ); + } + + Widget _buildQuadrant({ + required String title, + required int count, + required Color textColor, + required Color dividerColor, + required Color borderColor, + required List items, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.todoCardBg, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: borderColor, width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 15, + fontWeight: FontWeight.w700, + color: textColor, + ), + ), + Text( + '$count项', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 12, + fontWeight: FontWeight.w700, + color: textColor, + ), + ), + ], + ), + const SizedBox(height: 8), + Container(height: 1, color: dividerColor), + const SizedBox(height: 8), + ...items.map((item) => _buildTodoItem(item)), + ], + ), + ); + } + + Widget _buildTodoItem(String title) { + return SizedBox( + height: 42, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 13, + fontWeight: FontWeight.w600, + color: AppColors.slate700, + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: AppColors.slate300, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: AppColors.slate300, + shape: BoxShape.circle, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildBottomDock() { + return SizedBox( + height: 61, + child: Padding( + padding: const EdgeInsets.only( + left: 20, + right: 20, + top: 12, + bottom: 18, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 102, + height: 52, + padding: const EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + color: AppColors.todoToggleBg, + borderRadius: BorderRadius.circular(24), + border: Border.all(color: AppColors.todoToggleBorder, width: 1), + ), + child: Row( + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: AppColors.todoToggleActiveBg, + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: AppColors.todoToggleActiveBorder, + width: 1, + ), + ), + child: const Icon( + LucideIcons.listTodo, + size: 20, + color: AppColors.blue600, + ), + ), + const SizedBox(width: 4), + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + ), + child: const Icon( + LucideIcons.calendar, + size: 20, + color: AppColors.slate500, + ), + ), + ], + ), + ), + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: AppColors.todoHomeBtnBg, + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: AppColors.todoHomeBtnBorder, + width: 1, + ), + ), + child: const Icon( + LucideIcons.home, + size: 20, + color: Color(0xFF1E3A8A), + ), + ), + ], + ), + ), + ); + } +}