From cb45f7db16624ecd61f761958497604e681c9428 Mon Sep 17 00:00:00 2001 From: efrilm Date: Wed, 13 Aug 2025 13:55:34 +0700 Subject: [PATCH] feat: schedule page --- .../pages/home/widgets/feature.dart | 2 +- .../pages/schedule/schedule_page.dart | 1086 +++++++++++++++++ lib/presentation/router/app_router.dart | 3 + lib/presentation/router/app_router.gr.dart | 90 +- pubspec.lock | 16 + pubspec.yaml | 1 + 6 files changed, 1161 insertions(+), 37 deletions(-) create mode 100644 lib/presentation/pages/schedule/schedule_page.dart diff --git a/lib/presentation/pages/home/widgets/feature.dart b/lib/presentation/pages/home/widgets/feature.dart index 8b77eaf..5516e16 100644 --- a/lib/presentation/pages/home/widgets/feature.dart +++ b/lib/presentation/pages/home/widgets/feature.dart @@ -75,7 +75,7 @@ class HomeFeature extends StatelessWidget { title: 'Jadwal', color: const Color(0xFF9C27B0), icon: LineIcons.calendar, - onTap: () {}, + onTap: () => context.router.push(ScheduleRoute()), ), HomeFeatureTile( title: 'Aset Tetap', diff --git a/lib/presentation/pages/schedule/schedule_page.dart b/lib/presentation/pages/schedule/schedule_page.dart new file mode 100644 index 0000000..15b4d68 --- /dev/null +++ b/lib/presentation/pages/schedule/schedule_page.dart @@ -0,0 +1,1086 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:table_calendar/table_calendar.dart'; + +import '../../../common/theme/theme.dart'; + +// Schedule model dengan optimasi +class Schedule { + final DateTime date; + final String title; + final String? subtitle; + final String? time; + final Color color; + final IconData? icon; + final String? avatarUrl; + final bool hasDetails; + final String? location; + final String? instructions; + + const Schedule({ + required this.date, + required this.title, + this.subtitle, + this.time, + required this.color, + this.icon, + this.avatarUrl, + this.hasDetails = false, + this.location, + this.instructions, + }); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Schedule && + runtimeType == other.runtimeType && + date == other.date; + + @override + int get hashCode => date.hashCode; +} + +@RoutePage() +class SchedulePage extends StatefulWidget { + const SchedulePage({super.key}); + + @override + State createState() => _SchedulePageState(); +} + +class _SchedulePageState extends State + with TickerProviderStateMixin { + late DateTime _selectedDay; + late DateTime _focusedDay; + late PageController _pageController; + late AnimationController _fadeController; + late AnimationController _slideController; + late AnimationController _rotationController; + late Animation _fadeAnimation; + late Animation _slideAnimation; + late Animation rotationAnimation; + + final CalendarFormat _calendarFormat = CalendarFormat.week; + + // Optimized schedules with const constructor + static final List _schedules = [ + Schedule( + date: DateTime.now(), + title: 'Morning Shift', + subtitle: '8am - 1pm (5h)', + time: '08:00', + color: AppColor.primary, + icon: Icons.work, + hasDetails: true, + location: 'Office Building A', + instructions: 'Check all equipment before starting', + ), + Schedule( + date: DateTime.now(), + title: 'Morning Shift', + subtitle: '8am - 1pm (5h)', + time: '08:00', + color: AppColor.primary, + icon: Icons.work, + hasDetails: true, + location: 'Office Building A', + instructions: 'Check all equipment before starting', + ), + Schedule( + date: DateTime.now(), + title: 'Morning Shift', + subtitle: '8am - 1pm (5h)', + time: '08:00', + color: AppColor.primary, + icon: Icons.work, + hasDetails: true, + location: 'Office Building A', + instructions: 'Check all equipment before starting', + ), + Schedule( + date: DateTime.now(), + title: 'Morning Shift', + subtitle: '8am - 1pm (5h)', + time: '08:00', + color: AppColor.primary, + icon: Icons.work, + hasDetails: true, + location: 'Office Building A', + instructions: 'Check all equipment before starting', + ), + Schedule( + date: DateTime.now(), + title: 'Morning Shift', + subtitle: '8am - 1pm (5h)', + time: '08:00', + color: AppColor.primary, + icon: Icons.work, + hasDetails: true, + location: 'Office Building A', + instructions: 'Check all equipment before starting', + ), + Schedule( + date: DateTime.now(), + title: 'Morning Shift', + subtitle: '8am - 1pm (5h)', + time: '08:00', + color: AppColor.primary, + icon: Icons.work, + hasDetails: true, + location: 'Office Building A', + instructions: 'Check all equipment before starting', + ), + Schedule( + date: DateTime.now().add(Duration(days: 1)), + title: 'Night Shift', + subtitle: '6pm - 11pm (5h)', + time: '18:00', + color: AppColor.warning, + icon: Icons.nights_stay, + hasDetails: true, + location: 'Warehouse B', + instructions: 'Close all the lights when you exit', + ), + Schedule( + date: DateTime.now().add(Duration(days: 2)), + title: 'Stand up Meeting', + subtitle: '30 minutes', + time: '09:00', + color: AppColor.info, + icon: Icons.group, + hasDetails: true, + location: 'Conference Room', + instructions: 'Prepare weekly report', + ), + Schedule( + date: DateTime.now().add(Duration(days: 3)), + title: 'Training Session', + subtitle: '2 hours', + time: '14:00', + color: AppColor.success, + icon: Icons.school, + hasDetails: true, + location: 'Training Center', + instructions: 'Bring notebook and pen', + ), + Schedule( + date: DateTime.now().add(Duration(days: 4)), + title: 'Client Meeting', + subtitle: '1 hour', + time: '10:00', + color: Color(0xFF9C27B0), + icon: Icons.business, + hasDetails: true, + location: 'Client Office', + instructions: 'Prepare presentation slides', + ), + ]; + + @override + void initState() { + super.initState(); + _selectedDay = DateTime.now(); + _focusedDay = DateTime.now(); + _pageController = PageController(); + + // Animation controllers + _fadeController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + + _slideController = AnimationController( + duration: const Duration(milliseconds: 400), + vsync: this, + ); + + _rotationController = AnimationController( + duration: const Duration(seconds: 20), + vsync: this, + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + + _slideAnimation = + Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic), + ); + + rotationAnimation = + Tween( + begin: 0.0, + end: 2 * 3.14159, // 360 degrees in radians + ).animate( + CurvedAnimation(parent: _rotationController, curve: Curves.linear), + ); + + // Start animations + _fadeController.forward(); + _slideController.forward(); + _rotationController.repeat(); // Infinite rotation + } + + @override + void dispose() { + _pageController.dispose(); + _fadeController.dispose(); + _slideController.dispose(); + _rotationController.dispose(); + super.dispose(); + } + + List _getEventsForDay(DateTime day) { + return _schedules.where((schedule) { + return isSameDay(schedule.date, day); + }).toList(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + body: AnimatedBuilder( + animation: _fadeAnimation, + builder: (context, child) { + return Opacity( + opacity: _fadeAnimation.value, + child: CustomScrollView( + physics: const BouncingScrollPhysics(), + slivers: [ + _buildSliverAppBar(), + _buildSliverCalendar(), + _buildSliverScheduleList(), + ], + ), + ); + }, + ), + ); + } + + Widget _buildSliverAppBar() { + return SliverAppBar( + expandedHeight: 120.0, + floating: false, + pinned: true, + backgroundColor: AppColor.primary, + actions: [ + Container( + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: IconButton( + onPressed: () => _showCalendarOptions(), + icon: Icon( + Icons.calendar_view_week, + color: AppColor.white, + size: 24, + ), + ), + ), + ], + flexibleSpace: FlexibleSpaceBar( + titlePadding: const EdgeInsets.only(left: 50, bottom: 16), + title: Text( + 'My Schedule', + style: TextStyle( + color: AppColor.white, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + background: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [AppColor.primary, AppColor.primary.withOpacity(0.8)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Stack( + children: [ + Positioned( + right: -20, + top: -20, + child: AnimatedBuilder( + animation: rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: rotationAnimation.value, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.1), + ), + ), + ); + }, + ), + ), + Positioned( + left: -30, + bottom: -30, + child: AnimatedBuilder( + animation: rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: -rotationAnimation.value * 0.5, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.05), + ), + ), + ); + }, + ), + ), + Positioned( + right: 80, + bottom: 30, + child: AnimatedBuilder( + animation: rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: -rotationAnimation.value * 0.2, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppColor.white.withOpacity(0.08), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildSliverCalendar() { + return SliverToBoxAdapter( + child: SlideTransition( + position: _slideAnimation, + child: Container( + margin: const EdgeInsets.all(20), + padding: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: TableCalendar( + firstDay: DateTime.utc(2023, 1, 1), + lastDay: DateTime.utc(2025, 12, 31), + focusedDay: _focusedDay, + selectedDayPredicate: (day) => isSameDay(_selectedDay, day), + calendarFormat: _calendarFormat, + eventLoader: _getEventsForDay, + availableCalendarFormats: const {CalendarFormat.week: 'Week'}, + headerStyle: HeaderStyle( + formatButtonVisible: false, + titleCentered: true, + leftChevronVisible: true, + rightChevronVisible: true, + headerPadding: const EdgeInsets.symmetric(vertical: 16), + titleTextStyle: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + leftChevronIcon: Icon( + Icons.chevron_left, + color: AppColor.textSecondary, + ), + rightChevronIcon: Icon( + Icons.chevron_right, + color: AppColor.textSecondary, + ), + ), + calendarStyle: CalendarStyle( + outsideDaysVisible: false, + weekendTextStyle: TextStyle( + color: AppColor.textLight, + fontWeight: FontWeight.w600, + ), + defaultTextStyle: TextStyle( + color: AppColor.textPrimary, + fontWeight: FontWeight.w600, + ), + selectedTextStyle: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w700, + ), + selectedDecoration: BoxDecoration( + color: AppColor.primary, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + todayDecoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.2), + shape: BoxShape.circle, + ), + todayTextStyle: TextStyle( + color: AppColor.primary, + fontWeight: FontWeight.w700, + ), + markerDecoration: BoxDecoration( + color: AppColor.warning, + shape: BoxShape.circle, + ), + markersMaxCount: 1, + markerMargin: const EdgeInsets.only(top: 5), + markerSize: 6, + ), + onDaySelected: (selectedDay, focusedDay) { + if (!isSameDay(_selectedDay, selectedDay)) { + setState(() { + _selectedDay = selectedDay; + _focusedDay = focusedDay; + }); + _animateScheduleChange(); + } + }, + onPageChanged: (focusedDay) { + _focusedDay = focusedDay; + }, + ), + ), + ), + ); + } + + Widget _buildSliverScheduleList() { + final selectedEvents = _getEventsForDay(_selectedDay); + + return selectedEvents.isEmpty + ? SliverFillRemaining( + child: SlideTransition( + position: _slideAnimation, + child: _buildEmptyState(), + ), + ) + : SliverPadding( + padding: const EdgeInsets.only( + top: 8, + left: 20, + right: 20, + bottom: 20, + ), + sliver: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return SlideTransition( + position: _slideAnimation, + child: AnimatedContainer( + duration: Duration(milliseconds: 200 + (index * 100)), + curve: Curves.easeOutCubic, + child: _buildModernScheduleItem( + selectedEvents[index], + index, + ), + ), + ); + }, childCount: selectedEvents.length), + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(32), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.event_available, + size: 64, + color: AppColor.primary.withOpacity(0.6), + ), + ), + const SizedBox(height: 24), + Text( + 'No events today', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + 'Enjoy your free time!', + style: TextStyle(fontSize: 16, color: AppColor.textSecondary), + ), + ], + ), + ); + } + + Widget _buildModernScheduleItem(Schedule schedule, int index) { + return TweenAnimationBuilder( + duration: Duration(milliseconds: 300 + (index * 100)), + tween: Tween(begin: 0.0, end: 1.0), + curve: Curves.easeOutCubic, + builder: (context, value, child) { + return Transform.translate( + offset: Offset(0, 20 * (1 - value)), + child: Opacity( + opacity: value, + child: Container( + margin: const EdgeInsets.only(bottom: 16), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: schedule.hasDetails + ? () => _showModernScheduleDetails(schedule) + : null, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: schedule.color.withOpacity(0.2), + width: 2, + ), + boxShadow: [ + BoxShadow( + color: schedule.color.withOpacity(0.1), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: Row( + children: [ + // Time indicator + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: schedule.color.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + schedule.time ?? 'All day', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + color: schedule.color, + ), + ), + ), + const SizedBox(width: 16), + + // Content + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + schedule.title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + if (schedule.subtitle != null) ...[ + const SizedBox(height: 4), + Text( + schedule.subtitle!, + style: TextStyle( + fontSize: 14, + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + ], + if (schedule.location != null) ...[ + const SizedBox(height: 4), + Row( + children: [ + Icon( + Icons.location_on, + size: 14, + color: AppColor.textLight, + ), + const SizedBox(width: 4), + Text( + schedule.location!, + style: TextStyle( + fontSize: 12, + color: AppColor.textLight, + ), + ), + ], + ), + ], + ], + ), + ), + + // Icon and arrow + if (schedule.icon != null) ...[ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: schedule.color.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + schedule.icon, + color: schedule.color, + size: 24, + ), + ), + const SizedBox(width: 12), + ], + + if (schedule.hasDetails) + Icon( + Icons.arrow_forward_ios, + size: 16, + color: AppColor.textLight, + ), + ], + ), + ), + ), + ), + ), + ), + ); + }, + ); + } + + void _animateScheduleChange() { + _slideController.reset(); + _slideController.forward(); + } + + void _showCalendarOptions() { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + builder: (context) => Container( + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: const EdgeInsets.only(top: 12), + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColor.border, + borderRadius: BorderRadius.circular(2), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Text( + 'Calendar Options', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + ), + ], + ), + ), + ); + } + + void _showModernScheduleDetails(Schedule schedule) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (context) => _buildModernScheduleDetailsModal(schedule), + ); + } + + Widget _buildModernScheduleDetailsModal(Schedule schedule) { + return TweenAnimationBuilder( + duration: const Duration(milliseconds: 400), + tween: Tween(begin: 0.0, end: 1.0), + curve: Curves.easeOutCubic, + builder: (context, value, child) { + return Transform.translate( + offset: Offset(0, 50 * (1 - value)), + child: Opacity( + opacity: value, + child: Container( + height: MediaQuery.of(context).size.height * 0.75, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Handle bar + Container( + margin: const EdgeInsets.only(top: 12), + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColor.border, + borderRadius: BorderRadius.circular(2), + ), + ), + + // Content + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header with color indicator + Row( + children: [ + Container( + width: 4, + height: 40, + decoration: BoxDecoration( + color: schedule.color, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + schedule.title, + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.w800, + color: AppColor.textPrimary, + ), + ), + if (schedule.time != null) + Text( + schedule.time!, + style: TextStyle( + fontSize: 16, + color: schedule.color, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + if (schedule.icon != null) + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: schedule.color.withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + schedule.icon, + color: schedule.color, + size: 32, + ), + ), + ], + ), + + const SizedBox(height: 24), + + // Duration and location info + if (schedule.subtitle != null || + schedule.location != null) ...[ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + if (schedule.subtitle != null) ...[ + Row( + children: [ + Icon( + Icons.access_time, + color: AppColor.textSecondary, + size: 20, + ), + const SizedBox(width: 12), + Text( + schedule.subtitle!, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ], + ), + if (schedule.location != null) + const SizedBox(height: 16), + ], + if (schedule.location != null) + Row( + children: [ + Icon( + Icons.location_on, + color: AppColor.textSecondary, + size: 20, + ), + const SizedBox(width: 12), + Text( + schedule.location!, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ], + ), + ], + ), + ), + const SizedBox(height: 24), + ], + + // Map placeholder + Container( + height: 180, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + gradient: LinearGradient( + colors: [ + schedule.color.withOpacity(0.1), + schedule.color.withOpacity(0.05), + ], + ), + ), + child: Stack( + children: [ + // Map grid pattern + CustomPaint( + size: const Size(double.infinity, 180), + painter: MapGridPainter( + color: schedule.color.withOpacity(0.1), + ), + ), + // Location pin + Positioned( + right: 40, + top: 60, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: schedule.color, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: schedule.color.withOpacity( + 0.3, + ), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: const Icon( + Icons.location_on, + color: Colors.white, + size: 24, + ), + ), + ), + ], + ), + ), + + const SizedBox(height: 24), + + // Instructions + if (schedule.instructions != null) ...[ + Text( + 'Instructions', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: schedule.color.withOpacity(0.2), + ), + ), + child: Text( + schedule.instructions!, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColor.textPrimary, + height: 1.5, + ), + ), + ), + const SizedBox(height: 24), + ], + + // PDF attachment + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.border), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.picture_as_pdf, + color: Colors.red, + size: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Instructions.pdf', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + Text( + '2.4 MB', + style: TextStyle( + fontSize: 12, + color: AppColor.textSecondary, + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: schedule.color.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.download, + color: schedule.color, + size: 20, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } +} + +// Custom painter for map grid +class MapGridPainter extends CustomPainter { + final Color color; + + MapGridPainter({required this.color}); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color + ..strokeWidth = 1; + + // Draw vertical lines + for (int i = 0; i <= 8; i++) { + final x = (size.width / 8) * i; + canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint); + } + + // Draw horizontal lines + for (int i = 0; i <= 6; i++) { + final y = (size.height / 6) * i; + canvas.drawLine(Offset(0, y), Offset(size.width, y), paint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index a4944c8..d25e104 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -27,5 +27,8 @@ class AppRouter extends RootStackRouter { // Form AutoRoute(page: DailyTasksFormRoute.page), + + // Schedule + AutoRoute(page: ScheduleRoute.page), ]; } diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index 2aa1b90..2916421 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -23,21 +23,23 @@ import 'package:apskel_owner_flutter/presentation/pages/profile/profile_page.dar as _i6; import 'package:apskel_owner_flutter/presentation/pages/report/report_page.dart' as _i7; -import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/schedule/schedule_page.dart' as _i8; -import 'package:apskel_owner_flutter/presentation/pages/transaction/transaction_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart' as _i9; -import 'package:auto_route/auto_route.dart' as _i10; +import 'package:apskel_owner_flutter/presentation/pages/transaction/transaction_page.dart' + as _i10; +import 'package:auto_route/auto_route.dart' as _i11; /// generated route for /// [_i1.DailyTasksFormPage] -class DailyTasksFormRoute extends _i10.PageRouteInfo { - const DailyTasksFormRoute({List<_i10.PageRouteInfo>? children}) +class DailyTasksFormRoute extends _i11.PageRouteInfo { + const DailyTasksFormRoute({List<_i11.PageRouteInfo>? children}) : super(DailyTasksFormRoute.name, initialChildren: children); static const String name = 'DailyTasksFormRoute'; - static _i10.PageInfo page = _i10.PageInfo( + static _i11.PageInfo page = _i11.PageInfo( name, builder: (data) { return const _i1.DailyTasksFormPage(); @@ -47,13 +49,13 @@ class DailyTasksFormRoute extends _i10.PageRouteInfo { /// generated route for /// [_i2.HomePage] -class HomeRoute extends _i10.PageRouteInfo { - const HomeRoute({List<_i10.PageRouteInfo>? children}) +class HomeRoute extends _i11.PageRouteInfo { + const HomeRoute({List<_i11.PageRouteInfo>? children}) : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; - static _i10.PageInfo page = _i10.PageInfo( + static _i11.PageInfo page = _i11.PageInfo( name, builder: (data) { return const _i2.HomePage(); @@ -63,13 +65,13 @@ class HomeRoute extends _i10.PageRouteInfo { /// generated route for /// [_i3.LanguagePage] -class LanguageRoute extends _i10.PageRouteInfo { - const LanguageRoute({List<_i10.PageRouteInfo>? children}) +class LanguageRoute extends _i11.PageRouteInfo { + const LanguageRoute({List<_i11.PageRouteInfo>? children}) : super(LanguageRoute.name, initialChildren: children); static const String name = 'LanguageRoute'; - static _i10.PageInfo page = _i10.PageInfo( + static _i11.PageInfo page = _i11.PageInfo( name, builder: (data) { return const _i3.LanguagePage(); @@ -79,13 +81,13 @@ class LanguageRoute extends _i10.PageRouteInfo { /// generated route for /// [_i4.LoginPage] -class LoginRoute extends _i10.PageRouteInfo { - const LoginRoute({List<_i10.PageRouteInfo>? children}) +class LoginRoute extends _i11.PageRouteInfo { + const LoginRoute({List<_i11.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; - static _i10.PageInfo page = _i10.PageInfo( + static _i11.PageInfo page = _i11.PageInfo( name, builder: (data) { return const _i4.LoginPage(); @@ -95,13 +97,13 @@ class LoginRoute extends _i10.PageRouteInfo { /// generated route for /// [_i5.MainPage] -class MainRoute extends _i10.PageRouteInfo { - const MainRoute({List<_i10.PageRouteInfo>? children}) +class MainRoute extends _i11.PageRouteInfo { + const MainRoute({List<_i11.PageRouteInfo>? children}) : super(MainRoute.name, initialChildren: children); static const String name = 'MainRoute'; - static _i10.PageInfo page = _i10.PageInfo( + static _i11.PageInfo page = _i11.PageInfo( name, builder: (data) { return const _i5.MainPage(); @@ -111,13 +113,13 @@ class MainRoute extends _i10.PageRouteInfo { /// generated route for /// [_i6.ProfilePage] -class ProfileRoute extends _i10.PageRouteInfo { - const ProfileRoute({List<_i10.PageRouteInfo>? children}) +class ProfileRoute extends _i11.PageRouteInfo { + const ProfileRoute({List<_i11.PageRouteInfo>? children}) : super(ProfileRoute.name, initialChildren: children); static const String name = 'ProfileRoute'; - static _i10.PageInfo page = _i10.PageInfo( + static _i11.PageInfo page = _i11.PageInfo( name, builder: (data) { return const _i6.ProfilePage(); @@ -127,13 +129,13 @@ class ProfileRoute extends _i10.PageRouteInfo { /// generated route for /// [_i7.ReportPage] -class ReportRoute extends _i10.PageRouteInfo { - const ReportRoute({List<_i10.PageRouteInfo>? children}) +class ReportRoute extends _i11.PageRouteInfo { + const ReportRoute({List<_i11.PageRouteInfo>? children}) : super(ReportRoute.name, initialChildren: children); static const String name = 'ReportRoute'; - static _i10.PageInfo page = _i10.PageInfo( + static _i11.PageInfo page = _i11.PageInfo( name, builder: (data) { return const _i7.ReportPage(); @@ -142,33 +144,49 @@ class ReportRoute extends _i10.PageRouteInfo { } /// generated route for -/// [_i8.SplashPage] -class SplashRoute extends _i10.PageRouteInfo { - const SplashRoute({List<_i10.PageRouteInfo>? children}) - : super(SplashRoute.name, initialChildren: children); +/// [_i8.SchedulePage] +class ScheduleRoute extends _i11.PageRouteInfo { + const ScheduleRoute({List<_i11.PageRouteInfo>? children}) + : super(ScheduleRoute.name, initialChildren: children); - static const String name = 'SplashRoute'; + static const String name = 'ScheduleRoute'; - static _i10.PageInfo page = _i10.PageInfo( + static _i11.PageInfo page = _i11.PageInfo( name, builder: (data) { - return const _i8.SplashPage(); + return const _i8.SchedulePage(); }, ); } /// generated route for -/// [_i9.TransactionPage] -class TransactionRoute extends _i10.PageRouteInfo { - const TransactionRoute({List<_i10.PageRouteInfo>? children}) +/// [_i9.SplashPage] +class SplashRoute extends _i11.PageRouteInfo { + const SplashRoute({List<_i11.PageRouteInfo>? children}) + : super(SplashRoute.name, initialChildren: children); + + static const String name = 'SplashRoute'; + + static _i11.PageInfo page = _i11.PageInfo( + name, + builder: (data) { + return const _i9.SplashPage(); + }, + ); +} + +/// generated route for +/// [_i10.TransactionPage] +class TransactionRoute extends _i11.PageRouteInfo { + const TransactionRoute({List<_i11.PageRouteInfo>? children}) : super(TransactionRoute.name, initialChildren: children); static const String name = 'TransactionRoute'; - static _i10.PageInfo page = _i10.PageInfo( + static _i11.PageInfo page = _i11.PageInfo( name, builder: (data) { - return const _i9.TransactionPage(); + return const _i10.TransactionPage(); }, ); } diff --git a/pubspec.lock b/pubspec.lock index 880068d..de57a06 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1013,6 +1013,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + simple_gesture_detector: + dependency: transitive + description: + name: simple_gesture_detector + sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 + url: "https://pub.dev" + source: hosted + version: "0.2.1" sky_engine: dependency: transitive description: flutter @@ -1074,6 +1082,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + table_calendar: + dependency: "direct main" + description: + name: table_calendar + sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982" + url: "https://pub.dev" + source: hosted + version: "3.2.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 91e69aa..2739fa2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: another_flushbar: ^1.12.30 flutter_bloc: ^9.1.1 image_picker: ^1.1.2 + table_calendar: ^3.2.0 dev_dependencies: flutter_test: