timely_x 2.0.2
timely_x: ^2.0.2 copied to clipboard
A professional calendar library for Flutter with multi-resource scheduling and comprehensive customization.
TimelyX #
A powerful, flexible, and highly customizable calendar widget for Flutter that displays appointments across multiple resources (people, rooms, equipment, etc.) in day, week, and month views.
Perfect for scheduling applications, appointment systems, resource management, team calendars, and any scenario where you need to visualize time-based data across multiple entities.
β¨ Features #
Core Features #
- π Multiple View Types: Day, Week, and Month views
- π₯ Multi-Resource Support: Display appointments for multiple people, rooms, or resources
- π¨ Fully Customizable: 65+ theme properties for complete design control
- π±οΈ Rich Interactions: Tap, long-press, drag & drop, resize appointments
- π± Responsive: Works beautifully on mobile, tablet, and desktop
- π― Flexible Layouts: Resources-first or days-first week view layouts
- β‘ High Performance: Optimized rendering for smooth scrolling
- π Internationalization: Customizable date formats for any locale
Appointment Features #
- β° Real-time current time indicator
- π Drag and drop appointments between resources and time slots
- π Resize appointments to change duration
- π¨ Custom colors per appointment
- π Title, subtitle, and custom data support
- β οΈ Overlap detection and intelligent positioning
- π« Conflict prevention (optional)
UI Features #
- π Weekend highlighting
- π Today indicator
- π― Selection states
- π±οΈ Hover effects
- π Dark mode support
- βΏ Accessibility ready
- π Zebra striping for better readability
- π¨ Customizable grid, headers, and time columns
πΈ Screenshots #
Day View #
Shows all resources for a single day β perfect for detailed scheduling.

Week View #
Your standard weekly layout.

Week View β Resources First #
Displays each resource across the week β ideal for team scheduling.

Month View #
A monthly overview with appointment summaries.

Agenda View #
A vertical list of appointments across days.

π Getting Started #
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
timely_x: latest
intl: ^0.18.0 # Required for date formatting
Then run:
flutter pub get
Basic Usage #
import 'package:flutter/material.dart';
import 'package:timely_x/timely_x.dart';
class MyCalendar extends StatefulWidget {
@override
_MyCalendarState createState() => _MyCalendarState();
}
class _MyCalendarState extends State<MyCalendar> {
late CalendarController _controller;
@override
void initState() {
super.initState();
// Initialize controller
_controller = CalendarController(
config: CalendarConfig(
viewType: CalendarViewType.week,
hourHeight: 100,
dayStartHour: 8,
dayEndHour: 18,
),
);
// Add resources
_controller.updateResources([
DefaultResource(
id: '1',
name: 'John Doe',
color: Colors.blue,
),
DefaultResource(
id: '2',
name: 'Jane Smith',
color: Colors.green,
),
]);
// Add appointments
_controller.updateAppointments([
DefaultAppointment(
id: 'apt1',
resourceId: '1',
title: 'Team Meeting',
startTime: DateTime.now().add(Duration(hours: 1)),
endTime: DateTime.now().add(Duration(hours: 2)),
color: Colors.blue,
),
]);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Calendar'),
actions: [
IconButton(
icon: Icon(Icons.today),
onPressed: () => _controller.goToToday(),
),
],
),
body: CalendarView(
controller: _controller,
onAppointmentTap: (data) {
print('Tapped: ${data.appointment.title}');
},
onCellTap: (data) {
print('Cell tapped at ${data.dateTime}');
},
),
);
}
}
π Core Concepts #
1. CalendarController #
The controller manages the calendar state:
final controller = CalendarController(
initialDate: DateTime.now(),
config: CalendarConfig(
viewType: CalendarViewType.week,
hourHeight: 100,
dayStartHour: 8,
dayEndHour: 18,
),
);
// Navigation
controller.next(); // Go to next period
controller.previous(); // Go to previous period
controller.goToToday(); // Jump to today
controller.goToDate(date); // Jump to specific date
// Data management
controller.updateResources(resources);
controller.updateAppointments(appointments);
controller.addAppointment(appointment);
controller.removeAppointment(id);
// View type switching
controller.setViewType(CalendarViewType.day);
controller.setViewType(CalendarViewType.week);
controller.setViewType(CalendarViewType.month);
2. Resources #
Resources represent the entities (people, rooms, etc.) for which appointments are scheduled:
// Using the default implementation
final resource = DefaultResource(
id: 'user1',
name: 'John Doe',
avatarUrl: 'https://example.com/avatar.jpg',
color: Colors.blue,
category: 'Engineering',
isActive: true,
);
// Or create your own by extending CalendarResource
class Employee extends CalendarResource {
final String department;
final String email;
Employee({
required String id,
required String name,
required this.department,
required this.email,
});
@override
String get id => id;
@override
String get name => name;
}
3. Appointments #
Appointments represent scheduled events:
// Using the default implementation
final appointment = DefaultAppointment(
id: 'apt1',
resourceId: 'user1',
title: 'Team Meeting',
subtitle: 'Quarterly Review',
startTime: DateTime(2025, 11, 18, 14, 0),
endTime: DateTime(2025, 11, 18, 15, 30),
color: Colors.blue,
status: 'confirmed',
customData: {'location': 'Room 101'},
);
// Or create your own by extending CalendarAppointment
class Meeting extends CalendarAppointment {
final String location;
final List<String> attendees;
Meeting({
required String id,
required String resourceId,
required String title,
required DateTime startTime,
required DateTime endTime,
required this.location,
required this.attendees,
});
@override
String get id => id;
@override
String get resourceId => resourceId;
@override
String get title => title;
@override
DateTime get startTime => startTime;
@override
DateTime get endTime => endTime;
}
4. Configuration #
Fine-tune the calendar behavior:
final config = CalendarConfig(
viewType: CalendarViewType.week,
weekViewLayout: WeekViewLayout.resourcesFirst,
// Time range
dayStartHour: 8,
dayEndHour: 18,
// Sizing
hourHeight: 100.0,
minColumnWidth: 120.0,
maxColumnWidth: 300.0,
preferredColumnWidth: 180.0,
// Snap behavior
enableSnapping: true,
snapToMinutes: 15,
timeSlotDuration: Duration(minutes: 30),
// Features
enableDragAndDrop: true,
enableResize: true,
allowOverlapping: true,
maxOverlaps: 4,
showWeekends: true,
);
π¨ Customization #
The calendar is fully customizable with 65+ theme properties. Here's a quick example:
CalendarView(
controller: controller,
theme: CalendarTheme(
// Colors
todayHighlightColor: Colors.purple,
currentTimeIndicatorColor: Colors.orange,
weekendColor: Color(0xFFFFF3E0),
weekendTextColor: Colors.deepOrange,
// Text styles
timeTextStyle: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.grey[700],
),
appointmentTextStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.white,
),
// Date formats
timeFormat: 'h:mm a', // 12-hour format
dateFormat: 'dd', // Two-digit day
weekdayFormat: 'EEEE', // Full weekday name
// Spacing
appointmentPadding: EdgeInsets.all(8),
appointmentMargin: EdgeInsets.only(right: 4, bottom: 2),
resourceAvatarRadius: 24.0,
// Decorations
appointmentBorderRadius: 12.0,
appointmentShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
)
Theming Examples #
Dark Mode
CalendarTheme(
gridBackgroundColor: Color(0xFF121212),
headerBackgroundColor: Color(0xFF1E1E1E),
timeColumnBackgroundColor: Color(0xFF1E1E1E),
gridLineColor: Color(0xFF424242),
hourLineColor: Color(0xFF616161),
timeTextStyle: TextStyle(color: Color(0xFFB0B0B0)),
dateTextStyle: TextStyle(color: Color(0xFFE0E0E0)),
)
Compact Mobile
CalendarTheme(
resourceHeaderPadding: EdgeInsets.all(8),
appointmentPadding: EdgeInsets.all(2),
resourceAvatarRadius: 16.0,
timeTextStyle: TextStyle(fontSize: 11),
appointmentSpacing: 1.0,
)
European Locale
CalendarTheme(
timeFormat: 'HH:mm', // 24-hour
dateHeaderFormat: 'd MMMM yyyy', // 18 November 2025
weekdayFormat: 'EEEE', // Monday
monthFormat: 'MMMM yyyy', // November 2025
)
π For complete customization documentation, see:
- CUSTOMIZATION_GUIDE.md - Full customization guide
- THEME_QUICK_REFERENCE.md - Quick reference cheat sheet
π§ Advanced Features #
Custom Builders #
Replace default widgets with your own:
CalendarView(
controller: controller,
// Custom resource header
resourceHeaderBuilder: (context, resource, width, isHovered) {
return Container(
width: width,
padding: EdgeInsets.all(12),
child: Column(
children: [
Icon(Icons.person, size: 32),
SizedBox(height: 8),
Text(resource.name, style: TextStyle(fontWeight: FontWeight.bold)),
Text('Available', style: TextStyle(fontSize: 10, color: Colors.green)),
],
),
);
},
// Custom appointment widget
appointmentBuilder: (context, appointment, resource, rect, isSelected) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [appointment.color, appointment.color.withOpacity(0.7)],
),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(appointment.title, style: TextStyle(fontWeight: FontWeight.bold)),
Text('with ${resource.name}'),
],
),
);
},
// Custom time labels
timeColumnBuilder: (context, time, height, isHourMark) {
if (!isHourMark) return SizedBox.shrink();
return Container(
height: height,
alignment: Alignment.topRight,
padding: EdgeInsets.only(right: 8, top: 4),
child: Text(
DateFormat('h a').format(time),
style: TextStyle(fontSize: 11, color: Colors.grey),
),
);
},
)
Event Callbacks #
Handle user interactions:
CalendarView(
controller: controller,
// Appointment interactions
onAppointmentTap: (data) {
showDialog(
context: context,
builder: (context) => AppointmentDialog(appointment: data.appointment),
);
},
onAppointmentLongPress: (data) {
showModalBottomSheet(
context: context,
builder: (context) => AppointmentOptions(appointment: data.appointment),
);
},
onAppointmentSecondaryTap: (data) {
// Right-click or long-press
showContextMenu(context, data);
},
// Drag and drop
onAppointmentDragEnd: (data) {
// Update appointment with new time/resource
final updatedAppointment = data.appointment.copyWith(
resourceId: data.newResource.id,
startTime: data.newStartTime,
endTime: data.newEndTime,
);
controller.updateAppointment(updatedAppointment);
},
// Cell interactions
onCellTap: (data) {
// Create new appointment
createAppointment(
resource: data.resource,
startTime: data.dateTime,
);
},
onCellLongPress: (data) {
// Show quick create dialog
showQuickCreateDialog(context, data);
},
// Header interactions
onResourceHeaderTap: (data) {
showResourceDetails(data.resource);
},
onDateHeaderTap: (data) {
controller.goToDate(data.date);
},
)
Drag and Drop #
Full drag and drop support with customizable behavior:
CalendarConfig(
enableDragAndDrop: true,
enableResize: true,
enableSnapping: true,
snapToMinutes: 15,
allowOverlapping: true,
)
// Handle the drop
onAppointmentDragEnd: (data) {
// Check for conflicts
if (!controller.isTimeSlotAvailable(
resourceId: data.newResource.id,
startTime: data.newStartTime,
endTime: data.newEndTime,
excludeAppointmentId: data.appointment.id,
)) {
// Show conflict warning
showConflictDialog();
return;
}
// Update appointment
final updated = data.appointment.copyWith(
resourceId: data.newResource.id,
startTime: data.newStartTime,
endTime: data.newEndTime,
);
controller.updateAppointment(updated);
// Optionally save to backend
saveToBackend(updated);
}
Conflict Detection #
Prevent or warn about scheduling conflicts:
// Check availability before creating appointment
bool canSchedule = controller.isTimeSlotAvailable(
resourceId: 'user1',
startTime: proposedStart,
endTime: proposedEnd,
);
if (!canSchedule) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Scheduling Conflict'),
content: Text('This time slot is already booked.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
);
return;
}
// Create appointment
controller.addAppointment(newAppointment);
View Switching #
Seamlessly switch between different views:
Row(
children: [
TextButton(
onPressed: () => controller.setViewType(CalendarViewType.day),
child: Text('Day'),
),
TextButton(
onPressed: () => controller.setViewType(CalendarViewType.week),
child: Text('Week'),
),
TextButton(
onPressed: () => controller.setViewType(CalendarViewType.month),
child: Text('Month'),
),
],
)
Week View Layouts #
Choose between two layout modes:
// Resources first (default): Resource1[Day1, Day2...], Resource2[Day1, Day2...]
CalendarConfig(
weekViewLayout: WeekViewLayout.resourcesFirst,
)
// Days first: Day1[Resource1, Resource2...], Day2[Resource1, Resource2...]
CalendarConfig(
weekViewLayout: WeekViewLayout.daysFirst,
)
π± Responsive Design #
The calendar automatically adapts to different screen sizes:
LayoutBuilder(
builder: (context, constraints) {
final isMobile = constraints.maxWidth < 600;
final isTablet = constraints.maxWidth >= 600 && constraints.maxWidth < 1024;
return CalendarView(
controller: controller,
config: CalendarConfig(
minColumnWidth: isMobile ? 100 : 120,
preferredColumnWidth: isMobile ? 150 : 180,
hourHeight: isMobile ? 80 : 100,
),
theme: CalendarTheme(
appointmentPadding: isMobile
? EdgeInsets.all(2)
: EdgeInsets.all(4),
resourceAvatarRadius: isMobile ? 16 : 20,
),
);
},
)
π Internationalization #
Support any locale with custom date formats:
// German locale
CalendarTheme(
timeFormat: 'HH:mm',
dateFormat: 'd.',
weekdayFormat: 'EEEE',
dateHeaderFormat: 'd. MMMM yyyy',
monthFormat: 'MMMM yyyy',
)
// Japanese locale
CalendarTheme(
timeFormat: 'H:mm',
dateFormat: 'dζ₯',
weekdayFormat: 'EEEE',
dateHeaderFormat: 'yyyyεΉ΄Mζdζ₯',
monthFormat: 'yyyyεΉ΄Mζ',
)
// Spanish locale
CalendarTheme(
timeFormat: 'H:mm',
dateFormat: 'd',
weekdayFormat: 'EEEE',
dateHeaderFormat: "d 'de' MMMM 'de' yyyy",
monthFormat: "MMMM 'de' yyyy",
)
π Performance Tips #
- Use const constructors when possible:
const CalendarTheme(/* ... */)
- Reuse theme instances:
final myTheme = CalendarTheme(/* ... */);
// Reuse across multiple widgets
CalendarView(theme: myTheme)
- Limit visible appointments in month view:
CalendarTheme(
monthViewMaxVisibleAppointments: 3,
)
- Use efficient data structures:
// Good: Update all at once
controller.updateAppointments(allAppointments);
// Avoid: Multiple individual updates in a loop
for (var apt in appointments) {
controller.addAppointment(apt); // Triggers rebuild each time
}
π§ͺ Testing #
Example test setup:
testWidgets('Calendar displays appointments', (tester) async {
final controller = CalendarController(
config: CalendarConfig(viewType: CalendarViewType.week),
);
controller.updateResources([
DefaultResource(id: '1', name: 'Test User'),
]);
controller.updateAppointments([
DefaultAppointment(
id: 'apt1',
resourceId: '1',
title: 'Test Meeting',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 1)),
),
]);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CalendarView(controller: controller),
),
),
);
expect(find.text('Test Meeting'), findsOneWidget);
controller.dispose();
});
π API Reference #
CalendarView #
Main calendar widget that displays appointments across resources.
Properties:
controller(CalendarController, required) - Controls calendar stateconfig(CalendarConfig) - Configuration optionstheme(CalendarTheme) - Visual customizationresourceHeaderBuilder- Custom resource header widgetdateHeaderBuilder- Custom date header widgettimeColumnBuilder- Custom time column widgetappointmentBuilder- Custom appointment widgetemptyCellBuilder- Custom empty cell widgetcurrentTimeIndicatorBuilder- Custom time indicatoronAppointmentTap- Appointment tap callbackonAppointmentLongPress- Appointment long press callbackonAppointmentSecondaryTap- Appointment right-click callbackonCellTap- Cell tap callbackonCellLongPress- Cell long press callbackonAppointmentDragEnd- Drag end callbackonResourceHeaderTap- Resource header tap callbackonDateHeaderTap- Date header tap callback
CalendarController #
Manages calendar state and data.
Constructor:
CalendarController({
DateTime? initialDate,
required CalendarConfig config,
})
Properties:
currentDate(DateTime) - Currently displayed dateviewType(CalendarViewType) - Current view typevisibleDates(Listresources(Listappointments(ListselectedAppointment(CalendarAppointment?) - Selected appointment
Methods:
next()- Navigate to next periodprevious()- Navigate to previous periodgoToToday()- Jump to todaygoToDate(DateTime)- Jump to specific datesetViewType(CalendarViewType)- Change view typeupdateResources(List<CalendarResource>)- Update resourcesupdateAppointments(List<CalendarAppointment>)- Update appointmentsaddAppointment(CalendarAppointment)- Add single appointmentupdateAppointment(CalendarAppointment)- Update appointmentremoveAppointment(String)- Remove appointment by IDselectAppointment(CalendarAppointment?)- Select appointmentgetAppointmentsForResourceDate(String, DateTime)- Get appointmentsisTimeSlotAvailable(...)- Check availabilitygetViewPeriodDescription()- Get current period text
CalendarConfig #
Configuration for calendar behavior.
Properties:
viewType(CalendarViewType) - View type (day/week/month)weekViewLayout(WeekViewLayout) - Week view layout modedayStartHour(int) - Start hour (0-23)dayEndHour(int) - End hour (1-24)hourHeight(double) - Height of each hour in pixelsminColumnWidth(double) - Minimum column widthmaxColumnWidth(double) - Maximum column widthpreferredColumnWidth(double) - Preferred column widthtimeColumnWidth(double) - Time column widthresourceHeaderHeight(double) - Resource header heightdateHeaderHeight(double) - Date header heighttimeSlotDuration(Duration) - Time slot durationshowWeekends(bool) - Show weekendsenableSnapping(bool) - Enable time snappingsnapToMinutes(int) - Snap to nearest X minutesenableDragAndDrop(bool) - Enable drag and dropenableResize(bool) - Enable resizeallowOverlapping(bool) - Allow overlapping appointmentsmaxOverlaps(int) - Maximum overlaps
CalendarTheme #
Visual customization with 65+ properties. See CUSTOMIZATION_GUIDE.md for complete reference.
π€ Contributing #
Contributions are welcome! Please read our Contributing Guidelines before submitting a PR.
Development Setup #
# Clone the repository
git clone https://github.com/loicgeek/timely_x.git
# Get dependencies
flutter pub get
# Run tests
flutter test
# Run example
cd example
flutter run
Reporting Issues #
Please use the issue tracker to report bugs or request features.
π Examples #
Check out the /example directory for complete working examples:
- Basic Calendar - Simple setup with minimal configuration
- Custom Theme - Dark mode and custom styling
- Drag and Drop - Full drag and drop implementation
- Custom Builders - Custom widgets for all components
- Multi-Resource - Large-scale resource management
- Mobile Responsive - Responsive design patterns
- Localization - Multiple locale examples
Run the example app:
cd example
flutter run
π― Use Cases #
This calendar is perfect for:
- π₯ Team Scheduling - Schedule meetings across team members
- π¨ Room Booking - Manage conference room reservations
- π₯ Medical Appointments - Doctor scheduling systems
- π Service Booking - Salon, spa, or service appointments
- π Class Scheduling - School or training schedules
- π Vehicle Management - Fleet or rental car scheduling
- ποΈ Gym Classes - Fitness class and trainer scheduling
- πͺ Event Planning - Event and venue management
π License #
This project is licensed under the MIT License - see the LICENSE file for details.
π Acknowledgments #
- Built with Flutter
- Uses the intl package for internationalization
- Inspired by popular calendar libraries across platforms
π Support #
- π§ Email: loic.ngou98@gmail.com
- π Issues: GitHub Issues
- π Documentation: Full docs
πΊοΈ Roadmap #
- β All-day events section
- β Recurring appointments
- β Export to iCal/CSV
- β Timezone support
- β Agenda view
- β Resource grouping/filtering
- β Search and filters
- β Appointment templates
- β Accessibility improvements
- β More theme presets
β Star History #
If you find this package useful, please consider giving it a star on GitHub!
Made with β€οΈ by Loic Ngou