Tontine Calendar
A customizable Flutter calendar widget designed specifically for tontine applications with paginated months, configurable days, and contribution tracking.
Features
- Configurable Structure: Support for 2-12 months with 28-31 days each
- Tontine-Specific Logic: Tracks validated/unvalidated days and calculates contributions
- Data Emission: Emits comprehensive data when days are selected, including all preceding unvalidated days grouped by month
- Dual Selection Modes: Simple mode (sequential selection) and Multiple mode (range selection)
- Customizable Styling: Multiple built-in themes and full customization support
- Navigation: Month-to-month navigation with completion validation
- Accessibility: High contrast themes and proper semantic labels
Installation
Add this to your package's pubspec.yaml file:
dependencies:
tontine_calendar: ^1.0.0
Then run:
flutter pub get
Quick Start
import 'package:flutter/material.dart';
import 'package:tontine_calendar/tontine_calendar.dart';
class MyTontineCalendar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final config = TontineCalendarConfig(
monthCount: 6,
daysPerMonth: 31,
defaultDayAmount: 1000.0,
);
return TontineCalendar(
config: config,
onDaySelected: (data) {
print('Selected ${data.totalUnvalidatedDays} days');
print('Total amount: ${data.totalAmount}');
},
);
}
}
Configuration
Basic Configuration
final config = TontineCalendarConfig(
monthCount: 12, // 2-12 months
daysPerMonth: 31, // 28-31 days per month
defaultDayAmount: 500.0, // Default amount per day
initialMonth: 1, // Starting month (1-based)
);
With Validated Days
final validatedDays = [
TontineDay(day: 1, month: 1, isValidated: true, amount: 1000),
TontineDay(day: 2, month: 1, isValidated: true, amount: 1000),
// ... more validated days
];
final config = TontineCalendarConfig(
monthCount: 6,
daysPerMonth: 31,
validatedDays: validatedDays,
defaultDayAmount: 1000.0,
);
French Month Names
final config = TontineCalendarConfig.withFrenchNames(
monthCount: 12,
daysPerMonth: 31,
);
Styling and Themes
Built-in Themes
// Default theme
TontineCalendar(
config: config,
style: TontineCalendarStyle.defaultStyle(),
);
// Light theme
TontineCalendar(
config: config,
style: TontineCalendarStyle.lightTheme(),
);
// Dark theme
TontineCalendar(
config: config,
style: TontineCalendarStyle.darkTheme(),
);
// Material Design theme
TontineCalendar(
config: config,
style: TontineCalendarTheme.materialTheme(
colorScheme: Theme.of(context).colorScheme,
),
);
Custom Styling
final customStyle = TontineCalendarStyle(
regularDayColor: Colors.blue[100]!,
validatedDayColor: Colors.green,
selectedDayBorderColor: Colors.orange,
headerTextColor: Colors.blue[800]!,
dayBorderRadius: BorderRadius.circular(12.0),
);
TontineCalendar(
config: config,
style: customStyle,
);
Data Emission
When a day is selected, the calendar emits a TontineCalendarData object containing:
onDaySelected: (TontineCalendarData data) {
// Selected day information
print('Selected day: ${data.selectedDay.day}');
print('Selected month: ${data.selectedMonth.name}');
// All unvalidated days from previous months + current selection
print('Total unvalidated days: ${data.totalUnvalidatedDays}');
print('Total amount: ${data.totalAmount}');
// Grouped by month
data.unvalidatedDaysByMonth.forEach((month, days) {
print('Month $month: ${days.length} days');
});
}
Selection Modes
Simple Mode
Only the next sequential day can be selected:
TontineCalendar(
config: config,
simpleMode: true,
showModeSelection: false,
);
Multiple Mode
Allows range selection from next day to any future day:
TontineCalendar(
config: config,
simpleMode: false,
showModeSelection: true, // Shows mode toggle tabs
);
Advanced Usage
Handling Validated Day Taps
TontineCalendar(
config: config,
onValidatedDayTapped: (TontineDay day) {
// Show day details dialog
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Day ${day.day} Details'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Amount: ${day.amount}'),
Text('Date: ${day.validatedDate}'),
],
),
),
);
},
);
Custom Month Names
final config = TontineCalendarConfig(
monthCount: 4,
daysPerMonth: 30,
monthNames: ['Spring', 'Summer', 'Autumn', 'Winter'],
);
Disable Features
final config = TontineCalendarConfig(
monthCount: 6,
daysPerMonth: 31,
enableNavigation: false, // Disable month navigation
showCompletionStatus: false, // Hide completion indicators
showTotalAmount: false, // Hide amount display
showDayNumbers: false, // Hide day numbers
);
API Reference
TontineCalendarConfig
| Property | Type | Default | Description |
|---|---|---|---|
monthCount |
int |
12 |
Number of months (2-12) |
daysPerMonth |
int |
31 |
Days per month (28-31) |
monthNames |
List<String>? |
null |
Custom month names |
defaultDayAmount |
double? |
null |
Default amount per day |
validatedDays |
List<TontineDay>? |
null |
Pre-validated days |
enableNavigation |
bool |
true |
Enable month navigation |
showCompletionStatus |
bool |
true |
Show completion status |
showTotalAmount |
bool |
true |
Show total amounts |
initialMonth |
int |
1 |
Initial month to display |
TontineCalendarStyle
| Property | Type | Default | Description |
|---|---|---|---|
regularDayColor |
Color |
Color(0xff273071) |
Regular day background |
validatedDayColor |
Color |
Color(0xff0d7abc) |
Validated day background |
selectedDayBorderColor |
Color |
Color(0xff3bcc97) |
Selected day border |
dayBorderRadius |
BorderRadius |
BorderRadius.circular(8.0) |
Day container radius |
gridSpacing |
double |
10.0 |
Spacing between days |
dayHeight |
double |
50.0 |
Height of day containers |
TontineDay
| Property | Type | Description |
|---|---|---|
day |
int |
Day number (1-31) |
month |
int |
Month number (1-12) |
isValidated |
bool |
Whether day is validated |
amount |
double? |
Amount for this day |
validatedDate |
DateTime? |
When day was validated |
metadata |
Map<String, dynamic>? |
Additional data |
TontineCalendarData
| Property | Type | Description |
|---|---|---|
selectedDay |
TontineDay |
The selected day |
selectedMonth |
TontineMonth |
The selected month |
unvalidatedDaysByMonth |
Map<int, List<TontineDay>> |
Unvalidated days by month |
totalUnvalidatedDays |
int |
Total unvalidated days |
totalAmount |
double |
Total amount for unvalidated days |
Examples
Check out the /example folder for comprehensive examples including:
- Basic usage
- Configuration options
- Theme customization
- Interactive examples
To run the example:
cd example
flutter run
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.