├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .metadata
├── .vscode
├── launch.json
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── example
├── .gitignore
├── .metadata
├── README.md
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── example
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ │ ├── drawable-v21
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── values-night
│ │ │ │ └── styles.xml
│ │ │ │ └── values
│ │ │ │ └── styles.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
├── lib
│ ├── main.dart
│ ├── pages
│ │ ├── basics_example.dart
│ │ ├── complex_example.dart
│ │ ├── events_example.dart
│ │ ├── multi_example.dart
│ │ └── range_example.dart
│ └── utils.dart
├── pubspec.lock
├── pubspec.yaml
└── web
│ ├── favicon.png
│ ├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
│ ├── index.html
│ └── manifest.json
├── lib
├── src
│ ├── customization
│ │ ├── calendar_builders.dart
│ │ ├── calendar_style.dart
│ │ ├── days_of_week_style.dart
│ │ └── header_style.dart
│ ├── shared
│ │ └── utils.dart
│ ├── table_calendar.dart
│ ├── table_calendar_base.dart
│ └── widgets
│ │ ├── calendar_core.dart
│ │ ├── calendar_header.dart
│ │ ├── calendar_page.dart
│ │ ├── cell_content.dart
│ │ ├── custom_icon_button.dart
│ │ └── format_button.dart
└── table_calendar.dart
├── pubspec.lock
├── pubspec.yaml
└── test
├── calendar_header_test.dart
├── calendar_page_test.dart
├── cell_content_test.dart
├── common.dart
├── custom_icon_button_test.dart
├── format_button_test.dart
├── table_calendar_base_test.dart
├── table_calendar_test.dart
└── utils_test.dart
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Report unexpected behavior
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To reproduce**
14 | Please include a short code sample that can be used to reproduce the problem.
15 |
16 | Code sample
17 |
18 |
19 | Steps to reproduce the behavior:
20 | 1. Go to '...'
21 | 2. Click on '....'
22 | 3. Scroll down to '....'
23 | 4. See error
24 |
25 | **Expected behavior**
26 | A clear and concise description of what you expected to happen.
27 |
28 | **Screenshots**
29 | If applicable, add screenshots to help explain your problem.
30 |
31 | **Output of `flutter doctor`**
32 | Paste the result of this command here.
33 |
34 | Output
35 |
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for table_calendar
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # Flutter/Dart/Pub related
19 | **/doc/api/
20 | .dart_tool/
21 | .flutter-plugins
22 | .flutter-plugins-dependencies
23 | .packages
24 | .pub-cache/
25 | .pub/
26 | build/
27 |
28 | # Android related
29 | **/android/**/gradle-wrapper.jar
30 | **/android/.gradle
31 | **/android/captures/
32 | **/android/gradlew
33 | **/android/gradlew.bat
34 | **/android/local.properties
35 | **/android/**/GeneratedPluginRegistrant.java
36 |
37 | # iOS/XCode related
38 | **/ios/**/*.mode1v3
39 | **/ios/**/*.mode2v3
40 | **/ios/**/*.moved-aside
41 | **/ios/**/*.pbxuser
42 | **/ios/**/*.perspectivev3
43 | **/ios/**/*sync/
44 | **/ios/**/.sconsign.dblite
45 | **/ios/**/.tags*
46 | **/ios/**/.vagrant/
47 | **/ios/**/DerivedData/
48 | **/ios/**/Icon?
49 | **/ios/**/Pods/
50 | **/ios/**/.symlinks/
51 | **/ios/**/profile
52 | **/ios/**/xcuserdata
53 | **/ios/.generated/
54 | **/ios/Flutter/App.framework
55 | **/ios/Flutter/Flutter.framework
56 | **/ios/Flutter/Flutter.podspec
57 | **/ios/Flutter/Generated.xcconfig
58 | **/ios/Flutter/ephemeral
59 | **/ios/Flutter/app.flx
60 | **/ios/Flutter/app.zip
61 | **/ios/Flutter/flutter_assets/
62 | **/ios/Flutter/flutter_export_environment.sh
63 | **/ios/ServiceDefinitions.json
64 | **/ios/Runner/GeneratedPluginRegistrant.*
65 |
66 | # Exceptions to above rules.
67 | !**/ios/**/default.mode1v3
68 | !**/ios/**/default.mode2v3
69 | !**/ios/**/default.pbxuser
70 | !**/ios/**/default.perspectivev3
71 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: f30b7f4db93ee747cd727df747941a28ead25ff5
8 | channel: beta
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "example",
9 | "cwd": "example",
10 | "request": "launch",
11 | "type": "dart"
12 | },
13 | {
14 | "name": "example (profile mode)",
15 | "cwd": "example",
16 | "request": "launch",
17 | "type": "dart",
18 | "flutterMode": "profile"
19 | },
20 | {
21 | "name": "example (release mode)",
22 | "cwd": "example",
23 | "request": "launch",
24 | "type": "dart",
25 | "flutterMode": "release"
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dart.lineLength": 80,
3 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [3.2.0]
2 |
3 | * Added loadEventsForDisabledDays property to enable loading events for disabled days as well
4 | * Fixed empty weekendDays assertion issue
5 | * Updated intl version to 0.20.0
6 |
7 | ## [3.1.3]
8 |
9 | * Updated gradle config for example project
10 | * Added and applied lint rules, refactored code
11 |
12 | ## [3.1.2]
13 |
14 | * Added dayTextFormatter property to CalendarStyle that allows to customize the text within day cells
15 | * Reverted the default day cell's text formatting to just the day's number
16 |
17 | ## [3.1.1]
18 |
19 | * Added cell text localization based on current locale
20 |
21 | ## [3.1.0]
22 |
23 | * Upgraded to Dart 3
24 | * Updated intl version to 0.19.0
25 |
26 | ## [3.0.9]
27 |
28 | * Updated intl version to 0.18.0
29 | * Added explicit android:exported value to AndroidManifest
30 |
31 | ## [3.0.8]
32 |
33 | * Added tablePadding property to CalendarStyle
34 |
35 | ## [3.0.7]
36 |
37 | * Added week numbering feature
38 |
39 | ## [3.0.6]
40 |
41 | * Fixed issue with missing Flutter Web platform tag
42 |
43 | ## [3.0.5]
44 |
45 | * Added a visual indicator to FormatButton
46 | * Header buttons are now platform-aware
47 |
48 | ## [3.0.4]
49 |
50 | * Updated dependencies
51 | * Removed deprecated fields
52 |
53 | ## [3.0.3]
54 |
55 | * Added semantic label to prioritizedBuilder
56 | * Added tableBorder property to CalendarStyle
57 | * Added cellAlignment property to CalendarStyle
58 | * Added cellPadding property to CalendarStyle
59 |
60 | ## [3.0.2]
61 |
62 | * Improved semantic labels for screen readers
63 |
64 | ## [3.0.1]
65 |
66 | * Added pageAnimationEnabled property
67 | * Added currentDay property to improve widget testability
68 |
69 | ## [3.0.0]
70 |
71 | * Migrated to null safety
72 | * Removed CalendarController
73 | * Improved horizontal scrolling
74 | * Improved widget performance
75 | * Improved documentation
76 | * Added date range selection
77 | * Added multiple date selection
78 | * Added selective CalendarBuilders
79 | * Added firstDay and lastDay scroll boundaries
80 | * Added shouldFillViewport property
81 | * Added sixWeekMonthsEnforced property
82 | * Added more options to customize calendar's behavior
83 |
84 | ## [2.3.3]
85 |
86 | * Updated dependencies
87 |
88 | ## [2.3.2]
89 |
90 | * Added previousPage and nextPage methods to CalendarController
91 |
92 | ## [2.3.1]
93 |
94 | * Added chevron visibility properties to HeaderStyle
95 | * Added cellMargin property to CalendarStyle
96 | * Added eventDayStyle property to CalendarStyle
97 | * Added availableCalendarFormats dynamic update
98 | * Added optional BoxDecoration for each calendar row
99 | * Added optional BoxDecoration for days of week row
100 |
101 | ## [2.3.0]
102 |
103 | * Migrated to AndroidX
104 | * Added holidays to onDaySelected callback
105 | * Replaced deprecated overflow property with clipBehavior
106 |
107 | ## [2.2.3]
108 |
109 | * Added onCalendarCreated callback
110 |
111 | ## [2.2.2]
112 |
113 | * Added highlightSelected property to CalendarStyle
114 | * Added highlightToday property to CalendarStyle
115 |
116 | ## [2.2.1]
117 |
118 | * Added onHeaderTapped callback
119 | * Added onHeaderLongPressed callback
120 | * Fixed endDay issue
121 |
122 | ## [2.2.0]
123 |
124 | * Added LongPress Gesture support
125 | * Added option to disable days based on a predicate
126 | * Added option to hide DaysOfWeek row
127 | * Added header Decoration
128 | * Added headerMargin property
129 | * Added headerPadding property
130 | * Added contentPadding property
131 |
132 | ## [2.1.0]
133 |
134 | * Added dynamic events and holidays
135 | * Added StartingDayOfWeek for every weekday
136 | * Added support for custom weekend days
137 | * Added dowWeekdayBuilder and dowWeekendBuilder
138 | * Broadened intl dependency bounds
139 | * markersMaxAmount no longer affects markersBuilder
140 | * Fixed twoWeeks format programmatic issue
141 | * Fixed visibleDays issue
142 | * Fixed null dispose issue
143 |
144 | ## [2.0.2]
145 |
146 | * Updated dependencies
147 |
148 | ## [2.0.1]
149 |
150 | * Fixed issue with custom markers for holidays
151 |
152 | ## [2.0.0]
153 |
154 | * Added CalendarController - TableCalendar now features complete programmatic control
155 | * Removed redundant properties
156 | * Updated example project
157 | * Updated README
158 |
159 | ## [1.2.5]
160 |
161 | * Fixed last day of month animation issue
162 |
163 | ## [1.2.4]
164 |
165 | * Improved DateTime logic
166 | * Event markers can now be set to overflow cell boundaries
167 |
168 | ## [1.2.3]
169 |
170 | * Added startDay and endDay to allow users to specify available date range
171 | * Added unavailableStyle and unavailableDayBuilder for days outside of given date range
172 | * Added onUnavailableDaySelected callback
173 | * Unavailable days will not display event markers
174 |
175 | ## [1.2.2]
176 |
177 | * Fixed issue with Markers being null
178 |
179 | ## [1.2.1]
180 |
181 | * RowHeight can now be set as a fixed value
182 | * MaxMarkersAmount will now affect MarkersBuilder
183 |
184 | ## [1.2.0]
185 |
186 | * Added holiday support
187 | * Added holiday usage guide
188 | * Improved custom markers builder
189 | * Added rendering priority customization
190 | * Added FormatButton behavior customization
191 |
192 | ## [1.1.4]
193 |
194 | * Added TextBuilders for Header and DOW panel
195 | * Improved vertical swipe behavior
196 |
197 | ## [1.1.3]
198 |
199 | * Added title text customization with format skeleton
200 | * Added day of the week text customization with format skeleton
201 | * Rolled-back intl dependency
202 |
203 | ## [1.1.2]
204 |
205 | * Added locale support
206 | * Added locale usage guide
207 | * Updated example project
208 |
209 | ## [1.1.1]
210 |
211 | * Improved chevron customization
212 |
213 | ## [1.1.0]
214 |
215 | * Added programmatic selectedDay
216 | * Removed onFormatChanged callback - it is now integrated into onVisibleDaysChanged callback
217 | * Improved onVisibleDaysChanged behavior
218 | * Fixed issue with empty Calendar row
219 | * Changed default FormatButton texts
220 | * Updated example project
221 |
222 | ## [1.0.2]
223 |
224 | * FormatButton text can now be customized
225 |
226 | ## [1.0.1]
227 |
228 | * Fixed CalendarFormat issue when not using a callback
229 |
230 | ## [1.0.0]
231 |
232 | * Added custom Builders API
233 | * Added DateTime truncation logic
234 | * onDaySelected callback now contains list of events associated with that day
235 | * Added onVisibleDaysChanged callback
236 | * SwipeConfig can now be customized
237 | * Days outside of current month can be shown/hidden
238 | * Refactored code
239 | * Updated example project
240 |
241 | ## [0.3.2]
242 |
243 | * Added SwipeToExpand for CalendarFormat
244 | * AvailableGestures can now be specified (none, horizontalSwipe, verticalSwipe, all)
245 | * Fixed styling issue with SelectedDay on weekends
246 |
247 | ## [0.3.1]
248 |
249 | * Added slide animation for CalendarFormat
250 | * CalendarFormat animation can now be specified (slide, scale)
251 | * Added Monday-Sunday week format
252 | * Week format can now be specified with StartingDayOfWeek enum
253 |
254 | ## [0.3.0]
255 |
256 | * Any style can now be customized
257 | * Grouped properties into Classes
258 | * Refactored code for better readability
259 | * Added full documentation
260 |
261 | ## [0.2.2]
262 |
263 | * Added optional initial Date (defaults to DateTime.now())
264 |
265 | ## [0.2.1]
266 |
267 | * Added animated Swipe gesture
268 | * CalendarFormat can now be enforced programmatically
269 |
270 | ## [0.2.0]
271 |
272 | * Added animations to CalendarFormat change
273 | * Added animations to Date selection
274 | * Added new CalendarFormat - TwoWeeks
275 | * Available CalendarFormats can now be specified
276 |
277 | ## [0.1.4]
278 |
279 | * Refactored code
280 | * Updated example project
281 |
282 | ## [0.1.3]
283 |
284 | * Added chevron button customization
285 | * Calendar header can be hidden now
286 |
287 | ## [0.1.2]
288 |
289 | * Added OnFormatChanged callback
290 |
291 | ## [0.1.1]
292 |
293 | * Added CalendarFormat button customization
294 |
295 | ## [0.1.0]
296 |
297 | * Added CalendarFormat button - toggle between month view and week view
298 | * Additional customization is now available
299 |
300 | ## [0.0.2]
301 |
302 | * Revamped example
303 | * Improved description
304 |
305 | ## [0.0.1] - Initial release
306 |
307 | * Fully working TableCalendar; example included
308 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TableCalendar
2 |
3 | [](https://pub.dartlang.org/packages/table_calendar)
4 | [](https://github.com/Solido/awesome-flutter)
5 |
6 | Highly customizable, feature-packed calendar widget for Flutter.
7 |
8 | |  |  |
9 | | :------------: | :------------: |
10 | | **TableCalendar** with custom styles | **TableCalendar** with custom builders |
11 |
12 | ## Features
13 |
14 | * Extensive, yet easy to use API
15 | * Preconfigured UI with customizable styling
16 | * Custom selective builders for unlimited UI design
17 | * Locale support
18 | * Range selection support
19 | * Multiple selection support
20 | * Dynamic events and holidays
21 | * Vertical autosizing - fit the content, or fill the viewport
22 | * Multiple calendar formats (month, two weeks, week)
23 | * Horizontal swipe boundaries (first day, last day)
24 |
25 | ## Usage
26 |
27 | Make sure to check out [examples](https://github.com/aleksanderwozniak/table_calendar/tree/master/example/lib/pages) and [API docs](https://pub.dev/documentation/table_calendar/latest/) for more details.
28 |
29 | ### Installation
30 |
31 | Add the following line to `pubspec.yaml`:
32 |
33 | ```yaml
34 | dependencies:
35 | table_calendar: ^3.2.0
36 | ```
37 |
38 | ### Basic setup
39 |
40 | *The complete example is available [here](https://github.com/aleksanderwozniak/table_calendar/blob/master/example/lib/pages/basics_example.dart).*
41 |
42 | **TableCalendar** requires you to provide `firstDay`, `lastDay` and `focusedDay`:
43 | * `firstDay` is the first available day for the calendar. Users will not be able to access days before it.
44 | * `lastDay` is the last available day for the calendar. Users will not be able to access days after it.
45 | * `focusedDay` is the currently targeted day. Use this property to determine which month should be currently visible.
46 |
47 | ```dart
48 | TableCalendar(
49 | firstDay: DateTime.utc(2010, 10, 16),
50 | lastDay: DateTime.utc(2030, 3, 14),
51 | focusedDay: DateTime.now(),
52 | );
53 | ```
54 |
55 | #### Adding interactivity
56 |
57 | You will surely notice that previously set up calendar widget isn't quite interactive - you can only swipe it horizontally, to change the currently visible month. While it may be sufficient in certain situations, you can easily bring it to life by specifying a couple of callbacks.
58 |
59 | Adding the following code to the calendar widget will allow it to respond to user's taps, marking the tapped day as selected:
60 |
61 | ```dart
62 | selectedDayPredicate: (day) {
63 | return isSameDay(_selectedDay, day);
64 | },
65 | onDaySelected: (selectedDay, focusedDay) {
66 | setState(() {
67 | _selectedDay = selectedDay;
68 | _focusedDay = focusedDay; // update `_focusedDay` here as well
69 | });
70 | },
71 | ```
72 |
73 | In order to dynamically update visible calendar format, add those lines to the widget:
74 |
75 | ```dart
76 | calendarFormat: _calendarFormat,
77 | onFormatChanged: (format) {
78 | setState(() {
79 | _calendarFormat = format;
80 | });
81 | },
82 | ```
83 |
84 | Those two changes will make the calendar interactive and responsive to user's input.
85 |
86 | #### Updating focusedDay
87 |
88 | Setting `focusedDay` to a static value means that whenever **TableCalendar** widget rebuilds, it will use that specific `focusedDay`. You can quickly test it by using hot reload: set `focusedDay` to `DateTime.now()`, swipe to next month and trigger a hot reload - the calendar will "reset" to its initial state. To prevent this from happening, you should store and update `focusedDay` whenever any callback exposes it.
89 |
90 | Add this one callback to complete the basic setup:
91 |
92 | ```dart
93 | onPageChanged: (focusedDay) {
94 | _focusedDay = focusedDay;
95 | },
96 | ```
97 |
98 | It is worth noting that you don't need to call `setState()` inside `onPageChanged()` callback. You should just update the stored value, so that if the widget gets rebuilt later on, it will use the proper `focusedDay`.
99 |
100 | *The complete example is available [here](https://github.com/aleksanderwozniak/table_calendar/blob/master/example/lib/pages/basics_example.dart). You can find other examples [here](https://github.com/aleksanderwozniak/table_calendar/tree/master/example/lib/pages).*
101 |
102 | ### Events
103 |
104 | *The complete example is available [here](https://github.com/aleksanderwozniak/table_calendar/blob/master/example/lib/pages/events_example.dart).*
105 |
106 | You can supply custom events to **TableCalendar** widget. To do so, use `eventLoader` property - you will be given a `DateTime` object, to which you need to assign a list of events.
107 |
108 | ```dart
109 | eventLoader: (day) {
110 | return _getEventsForDay(day);
111 | },
112 | ```
113 |
114 | `_getEventsForDay()` can be of any implementation. For example, a `Map>` can be used:
115 |
116 | ```dart
117 | List _getEventsForDay(DateTime day) {
118 | return events[day] ?? [];
119 | }
120 | ```
121 |
122 | One thing worth remembering is that `DateTime` objects consist of both date and time parts. In many cases this time part is redundant for calendar related aspects.
123 |
124 | If you decide to use a `Map`, I suggest making it a `LinkedHashMap` - this will allow you to override equality comparison for two `DateTime` objects, comparing them just by their date parts:
125 |
126 | ```dart
127 | final events = LinkedHashMap(
128 | equals: isSameDay,
129 | hashCode: getHashCode,
130 | )..addAll(eventSource);
131 | ```
132 |
133 | #### Cyclic events
134 |
135 | `eventLoader` allows you to easily add events that repeat in a pattern. For example, this will add an event to every Monday:
136 |
137 | ```dart
138 | eventLoader: (day) {
139 | if (day.weekday == DateTime.monday) {
140 | return [Event('Cyclic event')];
141 | }
142 |
143 | return [];
144 | },
145 | ```
146 |
147 | #### Events selected on tap
148 |
149 | Often times having a sublist of events that are selected by tapping on a day is desired. You can achieve that by using the same method you provided to `eventLoader` inside of `onDaySelected` callback:
150 |
151 | ```dart
152 | void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
153 | if (!isSameDay(_selectedDay, selectedDay)) {
154 | setState(() {
155 | _focusedDay = focusedDay;
156 | _selectedDay = selectedDay;
157 | _selectedEvents = _getEventsForDay(selectedDay);
158 | });
159 | }
160 | }
161 | ```
162 |
163 | *The complete example is available [here](https://github.com/aleksanderwozniak/table_calendar/blob/master/example/lib/pages/events_example.dart).*
164 |
165 | ### Custom UI with CalendarBuilders
166 |
167 | To customize the UI with your own widgets, use [CalendarBuilders](https://pub.dev/documentation/table_calendar/latest/table_calendar/CalendarBuilders-class.html). Each builder can be used to selectively override the UI, allowing you to implement highly specific designs with minimal hassle.
168 |
169 | You can return `null` from any builder to use the default style. For example, the following snippet will override only the Sunday's day of the week label (Sun), leaving other dow labels unchanged:
170 |
171 | ```dart
172 | calendarBuilders: CalendarBuilders(
173 | dowBuilder: (context, day) {
174 | if (day.weekday == DateTime.sunday) {
175 | final text = DateFormat.E().format(day);
176 |
177 | return Center(
178 | child: Text(
179 | text,
180 | style: TextStyle(color: Colors.red),
181 | ),
182 | );
183 | }
184 | },
185 | ),
186 | ```
187 |
188 | ### Locale
189 |
190 | To display the calendar in desired language, use `locale` property.
191 | If you don't specify it, a default locale will be used.
192 |
193 | #### Initialization
194 |
195 | Before you can use a locale, you might need to initialize date formatting.
196 |
197 | A simple way of doing it is as follows:
198 | * First of all, add [intl](https://pub.dev/packages/intl) package to your pubspec.yaml file
199 | * Then make modifications to your `main()`:
200 |
201 | ```dart
202 | import 'package:intl/date_symbol_data_local.dart';
203 |
204 | void main() {
205 | initializeDateFormatting().then((_) => runApp(MyApp()));
206 | }
207 | ```
208 |
209 | After those two steps your app should be ready to use **TableCalendar** with different languages.
210 |
211 | #### Specifying a language
212 |
213 | To specify a language, simply pass it as a String code to `locale` property.
214 |
215 | For example, this will make **TableCalendar** use Polish language:
216 |
217 | ```dart
218 | TableCalendar(
219 | locale: 'pl_PL',
220 | ),
221 | ```
222 |
223 | |  |  |  |  |
224 | | :------------: | :------------: | :------------: | :------------: |
225 | | `'en_US'` | `'pl_PL'` | `'fr_FR'` | `'zh_CN'` |
226 |
227 | Note, that if you want to change the language of `FormatButton`'s text, you have to do this yourself. Use `availableCalendarFormats` property and pass the translated Strings there. Use i18n method of your choice.
228 |
229 | You can also hide the button altogether by setting `formatButtonVisible` to false.
230 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer to use the lint rule set from `package:lint`
2 |
3 | # include: package:lint/strict.yaml # For production apps
4 | # include: package:lint/casual.yaml # For code samples, hackathons and other non-production code
5 | include: package:lint/package.yaml # Use this for packages with public API
6 |
7 | # You might want to exclude auto-generated files from dart analysis
8 | analyzer:
9 | exclude:
10 | #- '**.freezed.dart'
11 | #- '**.g.dart'
12 |
13 | # You can customize the lint rules set to your own liking. A list of all rules
14 | # can be found at https://dart-lang.github.io/linter/lints/options/options.html
15 | linter:
16 | rules:
17 | # Util classes are awesome!
18 | # avoid_classes_with_only_static_members: false
19 |
20 | # Make constructors the first thing in every class
21 | # sort_constructors_first: true
22 |
23 | # Choose wisely, but you don't have to
24 | # prefer_double_quotes: true
25 | # prefer_single_quotes: true
26 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # Visual Studio Code related
19 | .vscode/
20 |
21 | # Flutter/Dart/Pub related
22 | **/doc/api/
23 | **/ios/Flutter/.last_build_id
24 | .dart_tool/
25 | .flutter-plugins
26 | .flutter-plugins-dependencies
27 | .packages
28 | .pub-cache/
29 | .pub/
30 | /build/
31 |
32 | # Web related
33 | lib/generated_plugin_registrant.dart
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: f30b7f4db93ee747cd727df747941a28ead25ff5
8 | channel: beta
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Table Calendar example
2 |
3 | Demonstrates how to use [table_calendar](https://pub.dartlang.org/packages/table_calendar) package.
4 | Displays **Table Calendar** widget with a `ListView` underneath it.
5 |
6 | |  |  |
7 | | :------------: | :------------: |
8 | | **Table Calendar** with custom styles | **Table Calendar** with Builders |
9 |
10 | **Table Calendar** offers a lot of customization:
11 | * by using custom Styles
12 | * by using custom Builders (accompanied by custom Styles)
13 |
14 | Using just Styles is a great way to get nice results with little effort.
15 | That being said, using Builders will give you full control over Calendar's UI.
16 |
17 | This example project will show you both of aforementioned methods.
18 |
19 | For more info please refer to [API docs](https://pub.dartlang.org/documentation/table_calendar/latest/table_calendar/table_calendar-library.html).
20 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | compileSdkVersion 34
27 | namespace "com.example.example"
28 |
29 | sourceSets {
30 | main.java.srcDirs += 'src/main/kotlin'
31 | }
32 |
33 | lintOptions {
34 | disable 'InvalidPackage'
35 | }
36 |
37 | defaultConfig {
38 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
39 | applicationId "com.example.example"
40 | minSdkVersion flutter.minSdkVersion
41 | targetSdkVersion 34
42 | versionCode flutterVersionCode.toInteger()
43 | versionName flutterVersionName
44 | }
45 |
46 | buildTypes {
47 | release {
48 | // TODO: Add your own signing config for the release build.
49 | // Signing with the debug keys for now, so `flutter run --release` works.
50 | signingConfig signingConfigs.debug
51 | }
52 | }
53 |
54 | kotlinOptions {
55 | jvmTarget = "1.8"
56 | }
57 |
58 | compileOptions {
59 | sourceCompatibility = JavaVersion.VERSION_1_8
60 | targetCompatibility = JavaVersion.VERSION_1_8
61 | }
62 | }
63 |
64 | flutter {
65 | source '../..'
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
12 |
20 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
35 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = '../build'
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(':app')
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.enableR8=true
5 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.4.1" apply false
22 | id "org.jetbrains.kotlin.android" version "2.0.0" apply false
23 | }
24 |
25 | include ":app"
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | table_calendar example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:intl/date_symbol_data_local.dart';
6 | import 'package:table_calendar_example/pages/basics_example.dart';
7 | import 'package:table_calendar_example/pages/complex_example.dart';
8 | import 'package:table_calendar_example/pages/events_example.dart';
9 | import 'package:table_calendar_example/pages/multi_example.dart';
10 | import 'package:table_calendar_example/pages/range_example.dart';
11 |
12 | void main() {
13 | initializeDateFormatting().then((_) => runApp(const MyApp()));
14 | }
15 |
16 | class MyApp extends StatelessWidget {
17 | const MyApp({super.key});
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return MaterialApp(
22 | title: 'TableCalendar Example',
23 | theme: ThemeData(
24 | primarySwatch: Colors.blue,
25 | ),
26 | home: const StartPage(),
27 | );
28 | }
29 | }
30 |
31 | class StartPage extends StatefulWidget {
32 | const StartPage({super.key});
33 |
34 | @override
35 | State createState() => _StartPageState();
36 | }
37 |
38 | class _StartPageState extends State {
39 | @override
40 | Widget build(BuildContext context) {
41 | return Scaffold(
42 | appBar: AppBar(
43 | title: const Text('TableCalendar Example'),
44 | ),
45 | body: Center(
46 | child: Column(
47 | mainAxisAlignment: MainAxisAlignment.center,
48 | children: [
49 | const SizedBox(height: 20.0),
50 | ElevatedButton(
51 | child: const Text('Basics'),
52 | onPressed: () => Navigator.push(
53 | context,
54 | MaterialPageRoute(builder: (_) => const TableBasicsExample()),
55 | ),
56 | ),
57 | const SizedBox(height: 12.0),
58 | ElevatedButton(
59 | child: const Text('Range Selection'),
60 | onPressed: () => Navigator.push(
61 | context,
62 | MaterialPageRoute(builder: (_) => const TableRangeExample()),
63 | ),
64 | ),
65 | const SizedBox(height: 12.0),
66 | ElevatedButton(
67 | child: const Text('Events'),
68 | onPressed: () => Navigator.push(
69 | context,
70 | MaterialPageRoute(builder: (_) => const TableEventsExample()),
71 | ),
72 | ),
73 | const SizedBox(height: 12.0),
74 | ElevatedButton(
75 | child: const Text('Multiple Selection'),
76 | onPressed: () => Navigator.push(
77 | context,
78 | MaterialPageRoute(builder: (_) => const TableMultiExample()),
79 | ),
80 | ),
81 | const SizedBox(height: 12.0),
82 | ElevatedButton(
83 | child: const Text('Complex'),
84 | onPressed: () => Navigator.push(
85 | context,
86 | MaterialPageRoute(builder: (_) => const TableComplexExample()),
87 | ),
88 | ),
89 | const SizedBox(height: 20.0),
90 | ],
91 | ),
92 | ),
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/example/lib/pages/basics_example.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:table_calendar/table_calendar.dart';
6 | import 'package:table_calendar_example/utils.dart';
7 |
8 | class TableBasicsExample extends StatefulWidget {
9 | const TableBasicsExample({super.key});
10 |
11 | @override
12 | State createState() => _TableBasicsExampleState();
13 | }
14 |
15 | class _TableBasicsExampleState extends State {
16 | CalendarFormat _calendarFormat = CalendarFormat.month;
17 | DateTime _focusedDay = DateTime.now();
18 | DateTime? _selectedDay;
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return Scaffold(
23 | appBar: AppBar(
24 | title: const Text('TableCalendar - Basics'),
25 | ),
26 | body: TableCalendar(
27 | firstDay: kFirstDay,
28 | lastDay: kLastDay,
29 | focusedDay: _focusedDay,
30 | calendarFormat: _calendarFormat,
31 | selectedDayPredicate: (day) {
32 | // Use `selectedDayPredicate` to determine which day is currently selected.
33 | // If this returns true, then `day` will be marked as selected.
34 |
35 | // Using `isSameDay` is recommended to disregard
36 | // the time-part of compared DateTime objects.
37 | return isSameDay(_selectedDay, day);
38 | },
39 | onDaySelected: (selectedDay, focusedDay) {
40 | if (!isSameDay(_selectedDay, selectedDay)) {
41 | // Call `setState()` when updating the selected day
42 | setState(() {
43 | _selectedDay = selectedDay;
44 | _focusedDay = focusedDay;
45 | });
46 | }
47 | },
48 | onFormatChanged: (format) {
49 | if (_calendarFormat != format) {
50 | // Call `setState()` when updating calendar format
51 | setState(() {
52 | _calendarFormat = format;
53 | });
54 | }
55 | },
56 | onPageChanged: (focusedDay) {
57 | // No need to call `setState()` here
58 | _focusedDay = focusedDay;
59 | },
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/example/lib/pages/complex_example.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // ignore_for_file: avoid_print
5 |
6 | import 'dart:collection';
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:intl/intl.dart';
10 | import 'package:table_calendar/table_calendar.dart';
11 | import 'package:table_calendar_example/utils.dart';
12 |
13 | class TableComplexExample extends StatefulWidget {
14 | const TableComplexExample({super.key});
15 |
16 | @override
17 | State createState() => _TableComplexExampleState();
18 | }
19 |
20 | class _TableComplexExampleState extends State {
21 | late final ValueNotifier> _selectedEvents;
22 | final ValueNotifier _focusedDay = ValueNotifier(DateTime.now());
23 | final Set _selectedDays = LinkedHashSet(
24 | equals: isSameDay,
25 | hashCode: getHashCode,
26 | );
27 |
28 | late PageController _pageController;
29 | CalendarFormat _calendarFormat = CalendarFormat.month;
30 | RangeSelectionMode _rangeSelectionMode = RangeSelectionMode.toggledOff;
31 | DateTime? _rangeStart;
32 | DateTime? _rangeEnd;
33 |
34 | @override
35 | void initState() {
36 | super.initState();
37 |
38 | _selectedDays.add(_focusedDay.value);
39 | _selectedEvents = ValueNotifier(_getEventsForDay(_focusedDay.value));
40 | }
41 |
42 | @override
43 | void dispose() {
44 | _focusedDay.dispose();
45 | _selectedEvents.dispose();
46 | super.dispose();
47 | }
48 |
49 | bool get canClearSelection =>
50 | _selectedDays.isNotEmpty || _rangeStart != null || _rangeEnd != null;
51 |
52 | List _getEventsForDay(DateTime day) {
53 | return kEvents[day] ?? [];
54 | }
55 |
56 | List _getEventsForDays(Iterable days) {
57 | return [
58 | for (final d in days) ..._getEventsForDay(d),
59 | ];
60 | }
61 |
62 | List _getEventsForRange(DateTime start, DateTime end) {
63 | final days = daysInRange(start, end);
64 | return _getEventsForDays(days);
65 | }
66 |
67 | void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
68 | setState(() {
69 | if (_selectedDays.contains(selectedDay)) {
70 | _selectedDays.remove(selectedDay);
71 | } else {
72 | _selectedDays.add(selectedDay);
73 | }
74 |
75 | _focusedDay.value = focusedDay;
76 | _rangeStart = null;
77 | _rangeEnd = null;
78 | _rangeSelectionMode = RangeSelectionMode.toggledOff;
79 | });
80 |
81 | _selectedEvents.value = _getEventsForDays(_selectedDays);
82 | }
83 |
84 | void _onRangeSelected(DateTime? start, DateTime? end, DateTime focusedDay) {
85 | setState(() {
86 | _focusedDay.value = focusedDay;
87 | _rangeStart = start;
88 | _rangeEnd = end;
89 | _selectedDays.clear();
90 | _rangeSelectionMode = RangeSelectionMode.toggledOn;
91 | });
92 |
93 | if (start != null && end != null) {
94 | _selectedEvents.value = _getEventsForRange(start, end);
95 | } else if (start != null) {
96 | _selectedEvents.value = _getEventsForDay(start);
97 | } else if (end != null) {
98 | _selectedEvents.value = _getEventsForDay(end);
99 | }
100 | }
101 |
102 | @override
103 | Widget build(BuildContext context) {
104 | return Scaffold(
105 | appBar: AppBar(
106 | title: const Text('TableCalendar - Complex'),
107 | ),
108 | body: Column(
109 | children: [
110 | ValueListenableBuilder(
111 | valueListenable: _focusedDay,
112 | builder: (context, value, _) {
113 | return _CalendarHeader(
114 | focusedDay: value,
115 | clearButtonVisible: canClearSelection,
116 | onTodayButtonTap: () {
117 | setState(() => _focusedDay.value = DateTime.now());
118 | },
119 | onClearButtonTap: () {
120 | setState(() {
121 | _rangeStart = null;
122 | _rangeEnd = null;
123 | _selectedDays.clear();
124 | _selectedEvents.value = [];
125 | });
126 | },
127 | onLeftArrowTap: () {
128 | _pageController.previousPage(
129 | duration: const Duration(milliseconds: 300),
130 | curve: Curves.easeOut,
131 | );
132 | },
133 | onRightArrowTap: () {
134 | _pageController.nextPage(
135 | duration: const Duration(milliseconds: 300),
136 | curve: Curves.easeOut,
137 | );
138 | },
139 | );
140 | },
141 | ),
142 | TableCalendar(
143 | firstDay: kFirstDay,
144 | lastDay: kLastDay,
145 | focusedDay: _focusedDay.value,
146 | headerVisible: false,
147 | selectedDayPredicate: (day) => _selectedDays.contains(day),
148 | rangeStartDay: _rangeStart,
149 | rangeEndDay: _rangeEnd,
150 | calendarFormat: _calendarFormat,
151 | rangeSelectionMode: _rangeSelectionMode,
152 | eventLoader: _getEventsForDay,
153 | holidayPredicate: (day) {
154 | // Every 20th day of the month will be treated as a holiday
155 | return day.day == 20;
156 | },
157 | onDaySelected: _onDaySelected,
158 | onRangeSelected: _onRangeSelected,
159 | onCalendarCreated: (controller) => _pageController = controller,
160 | onPageChanged: (focusedDay) => _focusedDay.value = focusedDay,
161 | onFormatChanged: (format) {
162 | if (_calendarFormat != format) {
163 | setState(() => _calendarFormat = format);
164 | }
165 | },
166 | ),
167 | const SizedBox(height: 8.0),
168 | Expanded(
169 | child: ValueListenableBuilder>(
170 | valueListenable: _selectedEvents,
171 | builder: (context, value, _) {
172 | return ListView.builder(
173 | itemCount: value.length,
174 | itemBuilder: (context, index) {
175 | return Container(
176 | margin: const EdgeInsets.symmetric(
177 | horizontal: 12.0,
178 | vertical: 4.0,
179 | ),
180 | decoration: BoxDecoration(
181 | border: Border.all(),
182 | borderRadius: BorderRadius.circular(12.0),
183 | ),
184 | child: ListTile(
185 | onTap: () => print('${value[index]}'),
186 | title: Text('${value[index]}'),
187 | ),
188 | );
189 | },
190 | );
191 | },
192 | ),
193 | ),
194 | ],
195 | ),
196 | );
197 | }
198 | }
199 |
200 | class _CalendarHeader extends StatelessWidget {
201 | final DateTime focusedDay;
202 | final VoidCallback onLeftArrowTap;
203 | final VoidCallback onRightArrowTap;
204 | final VoidCallback onTodayButtonTap;
205 | final VoidCallback onClearButtonTap;
206 | final bool clearButtonVisible;
207 |
208 | const _CalendarHeader({
209 | required this.focusedDay,
210 | required this.onLeftArrowTap,
211 | required this.onRightArrowTap,
212 | required this.onTodayButtonTap,
213 | required this.onClearButtonTap,
214 | required this.clearButtonVisible,
215 | });
216 |
217 | @override
218 | Widget build(BuildContext context) {
219 | final headerText = DateFormat.yMMM().format(focusedDay);
220 |
221 | return Padding(
222 | padding: const EdgeInsets.symmetric(vertical: 8.0),
223 | child: Row(
224 | children: [
225 | const SizedBox(width: 16.0),
226 | SizedBox(
227 | width: 120.0,
228 | child: Text(
229 | headerText,
230 | style: const TextStyle(fontSize: 26.0),
231 | ),
232 | ),
233 | IconButton(
234 | icon: const Icon(Icons.calendar_today, size: 20.0),
235 | visualDensity: VisualDensity.compact,
236 | onPressed: onTodayButtonTap,
237 | ),
238 | if (clearButtonVisible)
239 | IconButton(
240 | icon: const Icon(Icons.clear, size: 20.0),
241 | visualDensity: VisualDensity.compact,
242 | onPressed: onClearButtonTap,
243 | ),
244 | const Spacer(),
245 | IconButton(
246 | icon: const Icon(Icons.chevron_left),
247 | onPressed: onLeftArrowTap,
248 | ),
249 | IconButton(
250 | icon: const Icon(Icons.chevron_right),
251 | onPressed: onRightArrowTap,
252 | ),
253 | ],
254 | ),
255 | );
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/example/lib/pages/events_example.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // ignore_for_file: avoid_print
5 |
6 | import 'package:flutter/material.dart';
7 | import 'package:table_calendar/table_calendar.dart';
8 | import 'package:table_calendar_example/utils.dart';
9 |
10 | class TableEventsExample extends StatefulWidget {
11 | const TableEventsExample({super.key});
12 |
13 | @override
14 | State createState() => _TableEventsExampleState();
15 | }
16 |
17 | class _TableEventsExampleState extends State {
18 | late final ValueNotifier> _selectedEvents;
19 | CalendarFormat _calendarFormat = CalendarFormat.month;
20 | RangeSelectionMode _rangeSelectionMode = RangeSelectionMode
21 | .toggledOff; // Can be toggled on/off by longpressing a date
22 | DateTime _focusedDay = DateTime.now();
23 | DateTime? _selectedDay;
24 | DateTime? _rangeStart;
25 | DateTime? _rangeEnd;
26 |
27 | @override
28 | void initState() {
29 | super.initState();
30 |
31 | _selectedDay = _focusedDay;
32 | _selectedEvents = ValueNotifier(_getEventsForDay(_selectedDay!));
33 | }
34 |
35 | @override
36 | void dispose() {
37 | _selectedEvents.dispose();
38 | super.dispose();
39 | }
40 |
41 | List _getEventsForDay(DateTime day) {
42 | // Implementation example
43 | return kEvents[day] ?? [];
44 | }
45 |
46 | List _getEventsForRange(DateTime start, DateTime end) {
47 | // Implementation example
48 | final days = daysInRange(start, end);
49 |
50 | return [
51 | for (final d in days) ..._getEventsForDay(d),
52 | ];
53 | }
54 |
55 | void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
56 | if (!isSameDay(_selectedDay, selectedDay)) {
57 | setState(() {
58 | _selectedDay = selectedDay;
59 | _focusedDay = focusedDay;
60 | _rangeStart = null; // Important to clean those
61 | _rangeEnd = null;
62 | _rangeSelectionMode = RangeSelectionMode.toggledOff;
63 | });
64 |
65 | _selectedEvents.value = _getEventsForDay(selectedDay);
66 | }
67 | }
68 |
69 | void _onRangeSelected(DateTime? start, DateTime? end, DateTime focusedDay) {
70 | setState(() {
71 | _selectedDay = null;
72 | _focusedDay = focusedDay;
73 | _rangeStart = start;
74 | _rangeEnd = end;
75 | _rangeSelectionMode = RangeSelectionMode.toggledOn;
76 | });
77 |
78 | // `start` or `end` could be null
79 | if (start != null && end != null) {
80 | _selectedEvents.value = _getEventsForRange(start, end);
81 | } else if (start != null) {
82 | _selectedEvents.value = _getEventsForDay(start);
83 | } else if (end != null) {
84 | _selectedEvents.value = _getEventsForDay(end);
85 | }
86 | }
87 |
88 | @override
89 | Widget build(BuildContext context) {
90 | return Scaffold(
91 | appBar: AppBar(
92 | title: const Text('TableCalendar - Events'),
93 | ),
94 | body: Column(
95 | children: [
96 | TableCalendar(
97 | firstDay: kFirstDay,
98 | lastDay: kLastDay,
99 | focusedDay: _focusedDay,
100 | selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
101 | rangeStartDay: _rangeStart,
102 | rangeEndDay: _rangeEnd,
103 | calendarFormat: _calendarFormat,
104 | rangeSelectionMode: _rangeSelectionMode,
105 | eventLoader: _getEventsForDay,
106 | startingDayOfWeek: StartingDayOfWeek.monday,
107 | calendarStyle: const CalendarStyle(
108 | // Use `CalendarStyle` to customize the UI
109 | outsideDaysVisible: false,
110 | ),
111 | onDaySelected: _onDaySelected,
112 | onRangeSelected: _onRangeSelected,
113 | onFormatChanged: (format) {
114 | if (_calendarFormat != format) {
115 | setState(() {
116 | _calendarFormat = format;
117 | });
118 | }
119 | },
120 | onPageChanged: (focusedDay) {
121 | _focusedDay = focusedDay;
122 | },
123 | ),
124 | const SizedBox(height: 8.0),
125 | Expanded(
126 | child: ValueListenableBuilder>(
127 | valueListenable: _selectedEvents,
128 | builder: (context, value, _) {
129 | return ListView.builder(
130 | itemCount: value.length,
131 | itemBuilder: (context, index) {
132 | return Container(
133 | margin: const EdgeInsets.symmetric(
134 | horizontal: 12.0,
135 | vertical: 4.0,
136 | ),
137 | decoration: BoxDecoration(
138 | border: Border.all(),
139 | borderRadius: BorderRadius.circular(12.0),
140 | ),
141 | child: ListTile(
142 | onTap: () => print('${value[index]}'),
143 | title: Text('${value[index]}'),
144 | ),
145 | );
146 | },
147 | );
148 | },
149 | ),
150 | ),
151 | ],
152 | ),
153 | );
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/example/lib/pages/multi_example.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | // ignore_for_file: avoid_print
5 |
6 | import 'dart:collection';
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:table_calendar/table_calendar.dart';
10 | import 'package:table_calendar_example/utils.dart';
11 |
12 | class TableMultiExample extends StatefulWidget {
13 | const TableMultiExample({super.key});
14 |
15 | @override
16 | State createState() => _TableMultiExampleState();
17 | }
18 |
19 | class _TableMultiExampleState extends State {
20 | final ValueNotifier> _selectedEvents = ValueNotifier([]);
21 |
22 | // Using a `LinkedHashSet` is recommended due to equality comparison override
23 | final Set _selectedDays = LinkedHashSet(
24 | equals: isSameDay,
25 | hashCode: getHashCode,
26 | );
27 |
28 | CalendarFormat _calendarFormat = CalendarFormat.month;
29 | DateTime _focusedDay = DateTime.now();
30 |
31 | @override
32 | void dispose() {
33 | _selectedEvents.dispose();
34 | super.dispose();
35 | }
36 |
37 | List _getEventsForDay(DateTime day) {
38 | // Implementation example
39 | return kEvents[day] ?? [];
40 | }
41 |
42 | List _getEventsForDays(Set days) {
43 | // Implementation example
44 | // Note that days are in selection order (same applies to events)
45 | return [
46 | for (final d in days) ..._getEventsForDay(d),
47 | ];
48 | }
49 |
50 | void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
51 | setState(() {
52 | _focusedDay = focusedDay;
53 | // Update values in a Set
54 | if (_selectedDays.contains(selectedDay)) {
55 | _selectedDays.remove(selectedDay);
56 | } else {
57 | _selectedDays.add(selectedDay);
58 | }
59 | });
60 |
61 | _selectedEvents.value = _getEventsForDays(_selectedDays);
62 | }
63 |
64 | @override
65 | Widget build(BuildContext context) {
66 | return Scaffold(
67 | appBar: AppBar(
68 | title: const Text('TableCalendar - Multi'),
69 | ),
70 | body: Column(
71 | children: [
72 | TableCalendar(
73 | firstDay: kFirstDay,
74 | lastDay: kLastDay,
75 | focusedDay: _focusedDay,
76 | calendarFormat: _calendarFormat,
77 | eventLoader: _getEventsForDay,
78 | startingDayOfWeek: StartingDayOfWeek.monday,
79 | selectedDayPredicate: (day) {
80 | // Use values from Set to mark multiple days as selected
81 | return _selectedDays.contains(day);
82 | },
83 | onDaySelected: _onDaySelected,
84 | onFormatChanged: (format) {
85 | if (_calendarFormat != format) {
86 | setState(() {
87 | _calendarFormat = format;
88 | });
89 | }
90 | },
91 | onPageChanged: (focusedDay) {
92 | _focusedDay = focusedDay;
93 | },
94 | ),
95 | ElevatedButton(
96 | child: const Text('Clear selection'),
97 | onPressed: () {
98 | setState(() {
99 | _selectedDays.clear();
100 | _selectedEvents.value = [];
101 | });
102 | },
103 | ),
104 | const SizedBox(height: 8.0),
105 | Expanded(
106 | child: ValueListenableBuilder>(
107 | valueListenable: _selectedEvents,
108 | builder: (context, value, _) {
109 | return ListView.builder(
110 | itemCount: value.length,
111 | itemBuilder: (context, index) {
112 | return Container(
113 | margin: const EdgeInsets.symmetric(
114 | horizontal: 12.0,
115 | vertical: 4.0,
116 | ),
117 | decoration: BoxDecoration(
118 | border: Border.all(),
119 | borderRadius: BorderRadius.circular(12.0),
120 | ),
121 | child: ListTile(
122 | onTap: () => print('${value[index]}'),
123 | title: Text('${value[index]}'),
124 | ),
125 | );
126 | },
127 | );
128 | },
129 | ),
130 | ),
131 | ],
132 | ),
133 | );
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/example/lib/pages/range_example.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:table_calendar/table_calendar.dart';
6 | import 'package:table_calendar_example/utils.dart';
7 |
8 | class TableRangeExample extends StatefulWidget {
9 | const TableRangeExample({super.key});
10 |
11 | @override
12 | State createState() => _TableRangeExampleState();
13 | }
14 |
15 | class _TableRangeExampleState extends State {
16 | CalendarFormat _calendarFormat = CalendarFormat.month;
17 | RangeSelectionMode _rangeSelectionMode = RangeSelectionMode
18 | .toggledOn; // Can be toggled on/off by longpressing a date
19 | DateTime _focusedDay = DateTime.now();
20 | DateTime? _selectedDay;
21 | DateTime? _rangeStart;
22 | DateTime? _rangeEnd;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return Scaffold(
27 | appBar: AppBar(
28 | title: const Text('TableCalendar - Range'),
29 | ),
30 | body: TableCalendar(
31 | firstDay: kFirstDay,
32 | lastDay: kLastDay,
33 | focusedDay: _focusedDay,
34 | selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
35 | rangeStartDay: _rangeStart,
36 | rangeEndDay: _rangeEnd,
37 | calendarFormat: _calendarFormat,
38 | rangeSelectionMode: _rangeSelectionMode,
39 | onDaySelected: (selectedDay, focusedDay) {
40 | if (!isSameDay(_selectedDay, selectedDay)) {
41 | setState(() {
42 | _selectedDay = selectedDay;
43 | _focusedDay = focusedDay;
44 | _rangeStart = null; // Important to clean those
45 | _rangeEnd = null;
46 | _rangeSelectionMode = RangeSelectionMode.toggledOff;
47 | });
48 | }
49 | },
50 | onRangeSelected: (start, end, focusedDay) {
51 | setState(() {
52 | _selectedDay = null;
53 | _focusedDay = focusedDay;
54 | _rangeStart = start;
55 | _rangeEnd = end;
56 | _rangeSelectionMode = RangeSelectionMode.toggledOn;
57 | });
58 | },
59 | onFormatChanged: (format) {
60 | if (_calendarFormat != format) {
61 | setState(() {
62 | _calendarFormat = format;
63 | });
64 | }
65 | },
66 | onPageChanged: (focusedDay) {
67 | _focusedDay = focusedDay;
68 | },
69 | ),
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/example/lib/utils.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'dart:collection';
5 |
6 | import 'package:table_calendar/table_calendar.dart';
7 |
8 | /// Example event class.
9 | class Event {
10 | final String title;
11 |
12 | const Event(this.title);
13 |
14 | @override
15 | String toString() => title;
16 | }
17 |
18 | /// Example events.
19 | ///
20 | /// Using a [LinkedHashMap] is highly recommended if you decide to use a map.
21 | final kEvents = LinkedHashMap>(
22 | equals: isSameDay,
23 | hashCode: getHashCode,
24 | )..addAll(_kEventSource);
25 |
26 | final _kEventSource = {
27 | for (var item in List.generate(50, (index) => index))
28 | DateTime.utc(kFirstDay.year, kFirstDay.month, item * 5): List.generate(
29 | item % 4 + 1,
30 | (index) => Event('Event $item | ${index + 1}'),
31 | ),
32 | }..addAll({
33 | kToday: [
34 | const Event("Today's Event 1"),
35 | const Event("Today's Event 2"),
36 | ],
37 | });
38 |
39 | int getHashCode(DateTime key) {
40 | return key.day * 1000000 + key.month * 10000 + key.year;
41 | }
42 |
43 | /// Returns a list of [DateTime] objects from [first] to [last], inclusive.
44 | List daysInRange(DateTime first, DateTime last) {
45 | final dayCount = last.difference(first).inDays + 1;
46 | return List.generate(
47 | dayCount,
48 | (index) => DateTime.utc(first.year, first.month, first.day + index),
49 | );
50 | }
51 |
52 | final kToday = DateTime.now();
53 | final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
54 | final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
55 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.11.0"
12 | boolean_selector:
13 | dependency: transitive
14 | description:
15 | name: boolean_selector
16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "2.1.1"
20 | characters:
21 | dependency: transitive
22 | description:
23 | name: characters
24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "1.3.0"
28 | clock:
29 | dependency: transitive
30 | description:
31 | name: clock
32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.1.1"
36 | collection:
37 | dependency: transitive
38 | description:
39 | name: collection
40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.18.0"
44 | fake_async:
45 | dependency: transitive
46 | description:
47 | name: fake_async
48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.3.1"
52 | flutter:
53 | dependency: "direct main"
54 | description: flutter
55 | source: sdk
56 | version: "0.0.0"
57 | flutter_test:
58 | dependency: "direct dev"
59 | description: flutter
60 | source: sdk
61 | version: "0.0.0"
62 | http:
63 | dependency: transitive
64 | description:
65 | name: http
66 | sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
67 | url: "https://pub.dev"
68 | source: hosted
69 | version: "1.2.2"
70 | http_parser:
71 | dependency: transitive
72 | description:
73 | name: http_parser
74 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
75 | url: "https://pub.dev"
76 | source: hosted
77 | version: "4.0.2"
78 | intl:
79 | dependency: "direct main"
80 | description:
81 | name: intl
82 | sha256: "00f33b908655e606b86d2ade4710a231b802eec6f11e87e4ea3783fd72077a50"
83 | url: "https://pub.dev"
84 | source: hosted
85 | version: "0.20.1"
86 | leak_tracker:
87 | dependency: transitive
88 | description:
89 | name: leak_tracker
90 | sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
91 | url: "https://pub.dev"
92 | source: hosted
93 | version: "10.0.5"
94 | leak_tracker_flutter_testing:
95 | dependency: transitive
96 | description:
97 | name: leak_tracker_flutter_testing
98 | sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
99 | url: "https://pub.dev"
100 | source: hosted
101 | version: "3.0.5"
102 | leak_tracker_testing:
103 | dependency: transitive
104 | description:
105 | name: leak_tracker_testing
106 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
107 | url: "https://pub.dev"
108 | source: hosted
109 | version: "3.0.1"
110 | matcher:
111 | dependency: transitive
112 | description:
113 | name: matcher
114 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
115 | url: "https://pub.dev"
116 | source: hosted
117 | version: "0.12.16+1"
118 | material_color_utilities:
119 | dependency: transitive
120 | description:
121 | name: material_color_utilities
122 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
123 | url: "https://pub.dev"
124 | source: hosted
125 | version: "0.11.1"
126 | meta:
127 | dependency: transitive
128 | description:
129 | name: meta
130 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
131 | url: "https://pub.dev"
132 | source: hosted
133 | version: "1.15.0"
134 | path:
135 | dependency: transitive
136 | description:
137 | name: path
138 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
139 | url: "https://pub.dev"
140 | source: hosted
141 | version: "1.9.0"
142 | simple_gesture_detector:
143 | dependency: transitive
144 | description:
145 | name: simple_gesture_detector
146 | sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
147 | url: "https://pub.dev"
148 | source: hosted
149 | version: "0.2.1"
150 | sky_engine:
151 | dependency: transitive
152 | description: flutter
153 | source: sdk
154 | version: "0.0.99"
155 | source_span:
156 | dependency: transitive
157 | description:
158 | name: source_span
159 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
160 | url: "https://pub.dev"
161 | source: hosted
162 | version: "1.10.0"
163 | stack_trace:
164 | dependency: transitive
165 | description:
166 | name: stack_trace
167 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
168 | url: "https://pub.dev"
169 | source: hosted
170 | version: "1.11.1"
171 | stream_channel:
172 | dependency: transitive
173 | description:
174 | name: stream_channel
175 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
176 | url: "https://pub.dev"
177 | source: hosted
178 | version: "2.1.2"
179 | string_scanner:
180 | dependency: transitive
181 | description:
182 | name: string_scanner
183 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
184 | url: "https://pub.dev"
185 | source: hosted
186 | version: "1.2.0"
187 | table_calendar:
188 | dependency: "direct main"
189 | description:
190 | path: ".."
191 | relative: true
192 | source: path
193 | version: "3.2.0"
194 | term_glyph:
195 | dependency: transitive
196 | description:
197 | name: term_glyph
198 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
199 | url: "https://pub.dev"
200 | source: hosted
201 | version: "1.2.1"
202 | test_api:
203 | dependency: transitive
204 | description:
205 | name: test_api
206 | sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
207 | url: "https://pub.dev"
208 | source: hosted
209 | version: "0.7.2"
210 | typed_data:
211 | dependency: transitive
212 | description:
213 | name: typed_data
214 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
215 | url: "https://pub.dev"
216 | source: hosted
217 | version: "1.4.0"
218 | vector_math:
219 | dependency: transitive
220 | description:
221 | name: vector_math
222 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
223 | url: "https://pub.dev"
224 | source: hosted
225 | version: "2.1.4"
226 | vm_service:
227 | dependency: transitive
228 | description:
229 | name: vm_service
230 | sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
231 | url: "https://pub.dev"
232 | source: hosted
233 | version: "14.2.5"
234 | web:
235 | dependency: transitive
236 | description:
237 | name: web
238 | sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
239 | url: "https://pub.dev"
240 | source: hosted
241 | version: "1.1.0"
242 | sdks:
243 | dart: ">=3.5.0 <4.0.0"
244 | flutter: ">=3.18.0-18.0.pre.54"
245 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: table_calendar_example
2 | description: A short demo of table_calendar package.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=3.0.0 <4.0.0"
22 |
23 | dependencies:
24 | flutter:
25 | sdk: flutter
26 |
27 | intl: any
28 | table_calendar:
29 | path: ../
30 |
31 | dev_dependencies:
32 | flutter_test:
33 | sdk: flutter
34 |
35 | # For information on the generic Dart part of this file, see the
36 | # following page: https://dart.dev/tools/pub/pubspec
37 |
38 | # The following section is specific to Flutter.
39 | flutter:
40 |
41 | # The following line ensures that the Material Icons font is
42 | # included with your application, so that you can use the icons in
43 | # the material Icons class.
44 | uses-material-design: true
45 |
46 | # To add assets to your application, add an assets section, like this:
47 | # assets:
48 | # - images/a_dot_burr.jpeg
49 | # - images/a_dot_ham.jpeg
50 |
51 | # An image asset can refer to one or more resolution-specific "variants", see
52 | # https://flutter.dev/assets-and-images/#resolution-aware.
53 |
54 | # For details regarding adding assets from package dependencies, see
55 | # https://flutter.dev/assets-and-images/#from-packages
56 |
57 | # To add custom fonts to your application, add a fonts section here,
58 | # in this "flutter" section. Each entry in this list should have a
59 | # "family" key with the font family name, and a "fonts" key with a
60 | # list giving the asset and other descriptors for the font. For
61 | # example:
62 | # fonts:
63 | # - family: Schyler
64 | # fonts:
65 | # - asset: fonts/Schyler-Regular.ttf
66 | # - asset: fonts/Schyler-Italic.ttf
67 | # style: italic
68 | # - family: Trajan Pro
69 | # fonts:
70 | # - asset: fonts/TrajanPro.ttf
71 | # - asset: fonts/TrajanPro_Bold.ttf
72 | # weight: 700
73 | #
74 | # For details regarding fonts from package dependencies,
75 | # see https://flutter.dev/custom-fonts/#from-packages
76 |
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/web/favicon.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleksanderwozniak/table_calendar/5dc128b387204837d0937a0b16a00094957ed523/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | example
33 |
34 |
35 |
36 |
39 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "short_name": "example",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/customization/calendar_builders.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/widgets.dart';
5 | import 'package:table_calendar/src/shared/utils.dart'
6 | show DayBuilder, FocusedDayBuilder;
7 |
8 | /// Signature for a function that creates a single event marker for a given `day`.
9 | /// Contains a single `event` associated with that `day`.
10 | typedef SingleMarkerBuilder = Widget? Function(
11 | BuildContext context,
12 | DateTime day,
13 | T event,
14 | );
15 |
16 | /// Signature for a function that creates an event marker for a given `day`.
17 | /// Contains a list of `events` associated with that `day`.
18 | typedef MarkerBuilder = Widget? Function(
19 | BuildContext context,
20 | DateTime day,
21 | List events,
22 | );
23 |
24 | /// Signature for a function that creates a background highlight for a given `day`.
25 | ///
26 | /// Used for highlighting current range selection.
27 | /// Contains a value determining if the given `day` falls within the selected range.
28 | typedef HighlightBuilder = Widget? Function(
29 | BuildContext context,
30 | DateTime day,
31 | bool isWithinRange,
32 | );
33 |
34 | /// Class containing all custom builders for `TableCalendar`.
35 | class CalendarBuilders {
36 | /// Custom builder for day cells, with a priority over any other builder.
37 | final FocusedDayBuilder? prioritizedBuilder;
38 |
39 | /// Custom builder for a day cell that matches the current day.
40 | final FocusedDayBuilder? todayBuilder;
41 |
42 | /// Custom builder for day cells that are currently marked as selected by `selectedDayPredicate`.
43 | final FocusedDayBuilder? selectedBuilder;
44 |
45 | /// Custom builder for a day cell that is the start of current range selection.
46 | final FocusedDayBuilder? rangeStartBuilder;
47 |
48 | /// Custom builder for a day cell that is the end of current range selection.
49 | final FocusedDayBuilder? rangeEndBuilder;
50 |
51 | /// Custom builder for day cells that fall within the currently selected range.
52 | final FocusedDayBuilder? withinRangeBuilder;
53 |
54 | /// Custom builder for day cells, of which the `day.month` is different than `focusedDay.month`.
55 | /// This will affect day cells that do not match the currently focused month.
56 | final FocusedDayBuilder? outsideBuilder;
57 |
58 | /// Custom builder for day cells that have been disabled.
59 | ///
60 | /// This refers to dates disabled by returning false in `enabledDayPredicate`,
61 | /// as well as dates that are outside of the bounds set up by `firstDay` and `lastDay`.
62 | final FocusedDayBuilder? disabledBuilder;
63 |
64 | /// Custom builder for day cells that are marked as holidays by `holidayPredicate`.
65 | final FocusedDayBuilder? holidayBuilder;
66 |
67 | /// Custom builder for day cells that do not match any other builder.
68 | final FocusedDayBuilder? defaultBuilder;
69 |
70 | /// Custom builder for background highlight of range selection.
71 | /// If `isWithinRange` is true, then `day` is within the selected range.
72 | final HighlightBuilder? rangeHighlightBuilder;
73 |
74 | /// Custom builder for a single event marker. Each of those will be displayed in a `Row` above of the day cell.
75 | /// You can adjust markers' position with `CalendarStyle` properties.
76 | ///
77 | /// If `singleMarkerBuilder` is not specified, a default event marker will be displayed (customizable with `CalendarStyle`).
78 | final SingleMarkerBuilder? singleMarkerBuilder;
79 |
80 | /// Custom builder for event markers. Use to provide your own marker UI for each day cell.
81 | /// Using `markerBuilder` will override `singleMarkerBuilder` and default event markers.
82 | final MarkerBuilder? markerBuilder;
83 |
84 | /// Custom builder for days of the week labels (Mon, Tue, Wed, etc.).
85 | final DayBuilder? dowBuilder;
86 |
87 | /// Use to customize header's title using different widget
88 | final DayBuilder? headerTitleBuilder;
89 |
90 | /// Custom builder for number of the week labels.
91 | final Widget? Function(BuildContext context, int weekNumber)?
92 | weekNumberBuilder;
93 |
94 | /// Creates `CalendarBuilders` for `TableCalendar` widget.
95 | const CalendarBuilders({
96 | this.prioritizedBuilder,
97 | this.todayBuilder,
98 | this.selectedBuilder,
99 | this.rangeStartBuilder,
100 | this.rangeEndBuilder,
101 | this.withinRangeBuilder,
102 | this.outsideBuilder,
103 | this.disabledBuilder,
104 | this.holidayBuilder,
105 | this.defaultBuilder,
106 | this.rangeHighlightBuilder,
107 | this.singleMarkerBuilder,
108 | this.markerBuilder,
109 | this.dowBuilder,
110 | this.headerTitleBuilder,
111 | this.weekNumberBuilder,
112 | });
113 | }
114 |
--------------------------------------------------------------------------------
/lib/src/customization/calendar_style.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/widgets.dart';
5 | import 'package:table_calendar/table_calendar.dart';
6 |
7 | /// Class containing styling and configuration for `TableCalendar`'s content.
8 | class CalendarStyle {
9 | /// Maximum amount of single event marker dots to be displayed.
10 | final int markersMaxCount;
11 |
12 | /// Specifies if event markers rendered for a day cell can overflow cell's boundaries.
13 | /// * `true` - Event markers will be drawn over the cell boundaries
14 | /// * `false` - Event markers will be clipped if they are too big
15 | final bool canMarkersOverflow;
16 |
17 | /// Determines if single event marker dots should be aligned automatically with `markersAnchor`.
18 | /// If `false`, `markersOffset` will be used instead.
19 | final bool markersAutoAligned;
20 |
21 | /// Specifies the anchor point of single event markers if `markersAutoAligned` is `true`.
22 | /// A value of `0.5` will center the markers at the bottom edge of day cell's decoration.
23 | ///
24 | /// Includes `cellMargin` for calculations.
25 | final double markersAnchor;
26 |
27 | /// The size of single event marker dot.
28 | ///
29 | /// By default `markerSizeScale` is used. To use `markerSize` instead, simply provide a non-null value.
30 | final double? markerSize;
31 |
32 | /// Proportion of single event marker dot size in relation to day cell size.
33 | ///
34 | /// Includes `cellMargin` for calculations.
35 | final double markerSizeScale;
36 |
37 | /// `PositionedOffset` for event markers. Allows to specify `top`, `bottom`, `start` and `end`.
38 | final PositionedOffset markersOffset;
39 |
40 | /// General `Alignment` for event markers.
41 | /// Will have no effect on markers if `markersAutoAligned` or `markersOffset` is used.
42 | final AlignmentGeometry markersAlignment;
43 |
44 | /// Decoration of single event markers. Affects each marker dot.
45 | final Decoration markerDecoration;
46 |
47 | /// Margin of single event markers. Affects each marker dot.
48 | final EdgeInsets markerMargin;
49 |
50 | /// Margin of each individual day cell.
51 | final EdgeInsets cellMargin;
52 |
53 | /// Padding of each individual day cell.
54 | final EdgeInsets cellPadding;
55 |
56 | /// Alignment of each individual day cell.
57 | final AlignmentGeometry cellAlignment;
58 |
59 | /// Proportion of range selection highlight size in relation to day cell size.
60 | ///
61 | /// Includes `cellMargin` for calculations.
62 | final double rangeHighlightScale;
63 |
64 | /// Color of range selection highlight.
65 | final Color rangeHighlightColor;
66 |
67 | /// Determines if day cells that do not match the currently focused month should be visible.
68 | ///
69 | /// Affects only `CalendarFormat.month`.
70 | final bool outsideDaysVisible;
71 |
72 | /// Determines if a day cell that matches the current day should be highlighted.
73 | final bool isTodayHighlighted;
74 |
75 | /// TextStyle for a day cell that matches the current day.
76 | final TextStyle todayTextStyle;
77 |
78 | /// Decoration for a day cell that matches the current day.
79 | final Decoration todayDecoration;
80 |
81 | /// TextStyle for day cells that are currently marked as selected by `selectedDayPredicate`.
82 | final TextStyle selectedTextStyle;
83 |
84 | /// Decoration for day cells that are currently marked as selected by `selectedDayPredicate`.
85 | final Decoration selectedDecoration;
86 |
87 | /// TextStyle for a day cell that is the start of current range selection.
88 | final TextStyle rangeStartTextStyle;
89 |
90 | /// Decoration for a day cell that is the start of current range selection.
91 | final Decoration rangeStartDecoration;
92 |
93 | /// TextStyle for a day cell that is the end of current range selection.
94 | final TextStyle rangeEndTextStyle;
95 |
96 | /// Decoration for a day cell that is the end of current range selection.
97 | final Decoration rangeEndDecoration;
98 |
99 | /// TextStyle for day cells that fall within the currently selected range.
100 | final TextStyle withinRangeTextStyle;
101 |
102 | /// Decoration for day cells that fall within the currently selected range.
103 | final Decoration withinRangeDecoration;
104 |
105 | /// TextStyle for day cells, of which the `day.month` is different than `focusedDay.month`.
106 | /// This will affect day cells that do not match the currently focused month.
107 | final TextStyle outsideTextStyle;
108 |
109 | /// Decoration for day cells, of which the `day.month` is different than `focusedDay.month`.
110 | /// This will affect day cells that do not match the currently focused month.
111 | final Decoration outsideDecoration;
112 |
113 | /// TextStyle for day cells that have been disabled.
114 | ///
115 | /// This refers to dates disabled by returning false in `enabledDayPredicate`,
116 | /// as well as dates that are outside of the bounds set up by `firstDay` and `lastDay`.
117 | final TextStyle disabledTextStyle;
118 |
119 | /// Decoration for day cells that have been disabled.
120 | ///
121 | /// This refers to dates disabled by returning false in `enabledDayPredicate`,
122 | /// as well as dates that are outside of the bounds set up by `firstDay` and `lastDay`.
123 | final Decoration disabledDecoration;
124 |
125 | /// TextStyle for day cells that are marked as holidays by `holidayPredicate`.
126 | final TextStyle holidayTextStyle;
127 |
128 | /// Decoration for day cells that are marked as holidays by `holidayPredicate`.
129 | final Decoration holidayDecoration;
130 |
131 | /// TextStyle for day cells that match `weekendDay` list.
132 | final TextStyle weekendTextStyle;
133 |
134 | /// Decoration for day cells that match `weekendDay` list.
135 | final Decoration weekendDecoration;
136 |
137 | /// TextStyle for week number.
138 | final TextStyle weekNumberTextStyle;
139 |
140 | /// TextStyle for day cells that do not match any other styles.
141 | final TextStyle defaultTextStyle;
142 |
143 | /// Decoration for day cells that do not match any other styles.
144 | final Decoration defaultDecoration;
145 |
146 | /// Decoration for each interior row of day cells.
147 | final Decoration rowDecoration;
148 |
149 | /// Border for the internal `Table` widget.
150 | final TableBorder tableBorder;
151 |
152 | /// Padding for the internal `Table` widget.
153 | final EdgeInsets tablePadding;
154 |
155 | /// Use to customize the text within each day cell.
156 | /// Defaults to `'${date.day}'`, to show just the day number.
157 | ///
158 | /// Example usage:
159 | /// ```dart
160 | /// dayTextFormatter: (date, locale) => DateFormat.d(locale).format(date),
161 | /// ```
162 | final TextFormatter? dayTextFormatter;
163 |
164 | /// Creates a `CalendarStyle` used by `TableCalendar` widget.
165 | const CalendarStyle({
166 | this.isTodayHighlighted = true,
167 | this.canMarkersOverflow = true,
168 | this.outsideDaysVisible = true,
169 | this.markersAutoAligned = true,
170 | this.markerSize,
171 | this.markerSizeScale = 0.2,
172 | this.markersAnchor = 0.7,
173 | this.rangeHighlightScale = 1.0,
174 | this.markerMargin = const EdgeInsets.symmetric(horizontal: 0.3),
175 | this.markersAlignment = Alignment.bottomCenter,
176 | this.markersMaxCount = 4,
177 | this.cellMargin = const EdgeInsets.all(6.0),
178 | this.cellPadding = EdgeInsets.zero,
179 | this.cellAlignment = Alignment.center,
180 | this.markersOffset = const PositionedOffset(),
181 | this.rangeHighlightColor = const Color(0xFFBBDDFF),
182 | this.markerDecoration = const BoxDecoration(
183 | color: Color(0xFF263238),
184 | shape: BoxShape.circle,
185 | ),
186 | this.todayTextStyle = const TextStyle(
187 | color: Color(0xFFFAFAFA),
188 | fontSize: 16.0,
189 | ), //
190 | this.todayDecoration = const BoxDecoration(
191 | color: Color(0xFF9FA8DA),
192 | shape: BoxShape.circle,
193 | ),
194 | this.selectedTextStyle = const TextStyle(
195 | color: Color(0xFFFAFAFA),
196 | fontSize: 16.0,
197 | ),
198 | this.selectedDecoration = const BoxDecoration(
199 | color: Color(0xFF5C6BC0),
200 | shape: BoxShape.circle,
201 | ),
202 | this.rangeStartTextStyle = const TextStyle(
203 | color: Color(0xFFFAFAFA),
204 | fontSize: 16.0,
205 | ),
206 | this.rangeStartDecoration = const BoxDecoration(
207 | color: Color(0xFF6699FF),
208 | shape: BoxShape.circle,
209 | ),
210 | this.rangeEndTextStyle = const TextStyle(
211 | color: Color(0xFFFAFAFA),
212 | fontSize: 16.0,
213 | ),
214 | this.rangeEndDecoration = const BoxDecoration(
215 | color: Color(0xFF6699FF),
216 | shape: BoxShape.circle,
217 | ),
218 | this.withinRangeTextStyle = const TextStyle(),
219 | this.withinRangeDecoration = const BoxDecoration(shape: BoxShape.circle),
220 | this.outsideTextStyle = const TextStyle(color: Color(0xFFAEAEAE)),
221 | this.outsideDecoration = const BoxDecoration(shape: BoxShape.circle),
222 | this.disabledTextStyle = const TextStyle(color: Color(0xFFBFBFBF)),
223 | this.disabledDecoration = const BoxDecoration(shape: BoxShape.circle),
224 | this.holidayTextStyle = const TextStyle(color: Color(0xFF5C6BC0)),
225 | this.holidayDecoration = const BoxDecoration(
226 | border: Border.fromBorderSide(
227 | BorderSide(color: Color(0xFF9FA8DA), width: 1.4),
228 | ),
229 | shape: BoxShape.circle,
230 | ),
231 | this.weekendTextStyle = const TextStyle(color: Color(0xFF5A5A5A)),
232 | this.weekendDecoration = const BoxDecoration(shape: BoxShape.circle),
233 | this.weekNumberTextStyle =
234 | const TextStyle(fontSize: 12, color: Color(0xFFBFBFBF)),
235 | this.defaultTextStyle = const TextStyle(),
236 | this.defaultDecoration = const BoxDecoration(shape: BoxShape.circle),
237 | this.rowDecoration = const BoxDecoration(),
238 | this.tableBorder = const TableBorder(),
239 | this.tablePadding = EdgeInsets.zero,
240 | this.dayTextFormatter,
241 | });
242 | }
243 |
244 | /// Helper class containing data for internal `Positioned` widget.
245 | class PositionedOffset {
246 | /// Distance from the top edge.
247 | final double? top;
248 |
249 | /// Distance from the bottom edge.
250 | final double? bottom;
251 |
252 | /// Distance from the leading edge.
253 | final double? start;
254 |
255 | /// Distance from the trailing edge.
256 | final double? end;
257 |
258 | /// Creates a `PositionedOffset`. Values are set to `null` by default.
259 | const PositionedOffset({this.top, this.bottom, this.start, this.end});
260 | }
261 |
--------------------------------------------------------------------------------
/lib/src/customization/days_of_week_style.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/widgets.dart';
5 | import 'package:table_calendar/src/shared/utils.dart' show TextFormatter;
6 |
7 | /// Class containing styling for `TableCalendar`'s days of week panel.
8 | class DaysOfWeekStyle {
9 | /// Use to customize days of week panel text (e.g. with different `DateFormat`).
10 | /// You can use `String` transformations to further customize the text.
11 | /// Defaults to simple `'E'` format (i.e. Mon, Tue, Wed, etc.).
12 | ///
13 | /// Example usage:
14 | /// ```dart
15 | /// dowTextFormatter: (date, locale) => DateFormat.E(locale).format(date)[0],
16 | /// ```
17 | final TextFormatter? dowTextFormatter;
18 |
19 | /// Decoration for the top row of the table
20 | final Decoration decoration;
21 |
22 | /// Style for weekdays on the top of calendar.
23 | final TextStyle weekdayStyle;
24 |
25 | /// Style for weekend days on the top of calendar.
26 | final TextStyle weekendStyle;
27 |
28 | /// Creates a `DaysOfWeekStyle` used by `TableCalendar` widget.
29 | const DaysOfWeekStyle({
30 | this.dowTextFormatter,
31 | this.decoration = const BoxDecoration(),
32 | this.weekdayStyle = const TextStyle(color: Color(0xFF4F4F4F)),
33 | this.weekendStyle = const TextStyle(color: Color(0xFF6A6A6A)),
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/customization/header_style.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:table_calendar/src/shared/utils.dart' show TextFormatter;
6 |
7 | /// Class containing styling and configuration of `TableCalendar`'s header.
8 | class HeaderStyle {
9 | /// Responsible for making title Text centered.
10 | final bool titleCentered;
11 |
12 | /// Responsible for FormatButton visibility.
13 | final bool formatButtonVisible;
14 |
15 | /// Controls the text inside FormatButton.
16 | /// * `true` - the button will show next CalendarFormat
17 | /// * `false` - the button will show current CalendarFormat
18 | final bool formatButtonShowsNext;
19 |
20 | /// Use to customize header's title text (e.g. with different `DateFormat`).
21 | /// You can use `String` transformations to further customize the text.
22 | /// Defaults to simple `'yMMMM'` format (i.e. January 2019, February 2019, March 2019, etc.).
23 | ///
24 | /// Example usage:
25 | /// ```dart
26 | /// titleTextFormatter: (date, locale) => DateFormat.yM(locale).format(date),
27 | /// ```
28 | final TextFormatter? titleTextFormatter;
29 |
30 | /// Style for title Text (month-year) displayed in header.
31 | final TextStyle titleTextStyle;
32 |
33 | /// Style for FormatButton `Text`.
34 | final TextStyle formatButtonTextStyle;
35 |
36 | /// Background `Decoration` for FormatButton.
37 | final BoxDecoration formatButtonDecoration;
38 |
39 | /// Internal padding of the whole header.
40 | final EdgeInsets headerPadding;
41 |
42 | /// External margin of the whole header.
43 | final EdgeInsets headerMargin;
44 |
45 | /// Internal padding of FormatButton.
46 | final EdgeInsets formatButtonPadding;
47 |
48 | /// Internal padding of left chevron.
49 | /// Determines how much of ripple animation is visible during taps.
50 | final EdgeInsets leftChevronPadding;
51 |
52 | /// Internal padding of right chevron.
53 | /// Determines how much of ripple animation is visible during taps.
54 | final EdgeInsets rightChevronPadding;
55 |
56 | /// External margin of left chevron.
57 | final EdgeInsets leftChevronMargin;
58 |
59 | /// External margin of right chevron.
60 | final EdgeInsets rightChevronMargin;
61 |
62 | /// Widget used for left chevron.
63 | ///
64 | /// Tapping on it will navigate to previous calendar page.
65 | final Widget leftChevronIcon;
66 |
67 | /// Widget used for right chevron.
68 | ///
69 | /// Tapping on it will navigate to next calendar page.
70 | final Widget rightChevronIcon;
71 |
72 | /// Determines left chevron's visibility.
73 | final bool leftChevronVisible;
74 |
75 | /// Determines right chevron's visibility.
76 | final bool rightChevronVisible;
77 |
78 | /// Decoration of the header.
79 | final BoxDecoration decoration;
80 |
81 | /// Creates a `HeaderStyle` used by `TableCalendar` widget.
82 | const HeaderStyle({
83 | this.titleCentered = false,
84 | this.formatButtonVisible = true,
85 | this.formatButtonShowsNext = true,
86 | this.titleTextFormatter,
87 | this.titleTextStyle = const TextStyle(fontSize: 17.0),
88 | this.formatButtonTextStyle = const TextStyle(fontSize: 14.0),
89 | this.formatButtonDecoration = const BoxDecoration(
90 | border: Border.fromBorderSide(BorderSide()),
91 | borderRadius: BorderRadius.all(Radius.circular(12.0)),
92 | ),
93 | this.headerMargin = EdgeInsets.zero,
94 | this.headerPadding = const EdgeInsets.symmetric(vertical: 8.0),
95 | this.formatButtonPadding =
96 | const EdgeInsets.symmetric(horizontal: 10.0, vertical: 4.0),
97 | this.leftChevronPadding = const EdgeInsets.all(12.0),
98 | this.rightChevronPadding = const EdgeInsets.all(12.0),
99 | this.leftChevronMargin = const EdgeInsets.symmetric(horizontal: 8.0),
100 | this.rightChevronMargin = const EdgeInsets.symmetric(horizontal: 8.0),
101 | this.leftChevronIcon = const Icon(Icons.chevron_left),
102 | this.rightChevronIcon = const Icon(Icons.chevron_right),
103 | this.leftChevronVisible = true,
104 | this.rightChevronVisible = true,
105 | this.decoration = const BoxDecoration(),
106 | });
107 | }
108 |
--------------------------------------------------------------------------------
/lib/src/shared/utils.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/widgets.dart';
5 |
6 | /// Signature for a function that creates a widget for a given `day`.
7 | typedef DayBuilder = Widget? Function(BuildContext context, DateTime day);
8 |
9 | /// Signature for a function that creates a widget for a given `day`.
10 | /// Additionally, contains the currently focused day.
11 | typedef FocusedDayBuilder = Widget? Function(
12 | BuildContext context,
13 | DateTime day,
14 | DateTime focusedDay,
15 | );
16 |
17 | /// Signature for a function returning text that can be localized and formatted with `DateFormat`.
18 | typedef TextFormatter = String Function(DateTime date, dynamic locale);
19 |
20 | /// Gestures available for the calendar.
21 | enum AvailableGestures { none, verticalSwipe, horizontalSwipe, all }
22 |
23 | /// Formats that the calendar can display.
24 | enum CalendarFormat { month, twoWeeks, week }
25 |
26 | /// Days of the week that the calendar can start with.
27 | enum StartingDayOfWeek {
28 | monday,
29 | tuesday,
30 | wednesday,
31 | thursday,
32 | friday,
33 | saturday,
34 | sunday,
35 | }
36 |
37 | /// Returns a numerical value associated with given `weekday`.
38 | ///
39 | /// Returns 1 for `StartingDayOfWeek.monday`, all the way to 7 for `StartingDayOfWeek.sunday`.
40 | int getWeekdayNumber(StartingDayOfWeek weekday) {
41 | return StartingDayOfWeek.values.indexOf(weekday) + 1;
42 | }
43 |
44 | /// Returns `date` in UTC format, without its time part.
45 | DateTime normalizeDate(DateTime date) {
46 | return DateTime.utc(date.year, date.month, date.day);
47 | }
48 |
49 | /// Checks if two DateTime objects are the same day.
50 | /// Returns `false` if either of them is null.
51 | bool isSameDay(DateTime? a, DateTime? b) {
52 | if (a == null || b == null) {
53 | return false;
54 | }
55 |
56 | return a.year == b.year && a.month == b.month && a.day == b.day;
57 | }
58 |
--------------------------------------------------------------------------------
/lib/src/widgets/calendar_core.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:table_calendar/src/shared/utils.dart';
6 | import 'package:table_calendar/src/widgets/calendar_page.dart';
7 |
8 | class CalendarCore extends StatelessWidget {
9 | final DateTime? focusedDay;
10 | final DateTime firstDay;
11 | final DateTime lastDay;
12 | final CalendarFormat calendarFormat;
13 | final DayBuilder? dowBuilder;
14 | final DayBuilder? weekNumberBuilder;
15 | final FocusedDayBuilder dayBuilder;
16 | final bool sixWeekMonthsEnforced;
17 | final bool dowVisible;
18 | final bool weekNumbersVisible;
19 | final Decoration? dowDecoration;
20 | final Decoration? rowDecoration;
21 | final TableBorder? tableBorder;
22 | final EdgeInsets? tablePadding;
23 | final double? dowHeight;
24 | final double? rowHeight;
25 | final BoxConstraints constraints;
26 | final int? previousIndex;
27 | final StartingDayOfWeek startingDayOfWeek;
28 | final PageController? pageController;
29 | final ScrollPhysics? scrollPhysics;
30 | final void Function(int, DateTime) onPageChanged;
31 |
32 | const CalendarCore({
33 | super.key,
34 | this.dowBuilder,
35 | required this.dayBuilder,
36 | required this.onPageChanged,
37 | required this.firstDay,
38 | required this.lastDay,
39 | required this.constraints,
40 | this.dowHeight,
41 | this.rowHeight,
42 | this.startingDayOfWeek = StartingDayOfWeek.sunday,
43 | this.calendarFormat = CalendarFormat.month,
44 | this.pageController,
45 | this.focusedDay,
46 | this.previousIndex,
47 | this.sixWeekMonthsEnforced = false,
48 | this.dowVisible = true,
49 | this.weekNumberBuilder,
50 | required this.weekNumbersVisible,
51 | this.dowDecoration,
52 | this.rowDecoration,
53 | this.tableBorder,
54 | this.tablePadding,
55 | this.scrollPhysics,
56 | }) : assert(!dowVisible || (dowHeight != null && dowBuilder != null));
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | return PageView.builder(
61 | controller: pageController,
62 | physics: scrollPhysics,
63 | itemCount: _getPageCount(calendarFormat, firstDay, lastDay),
64 | itemBuilder: (context, index) {
65 | final baseDay = _getBaseDay(calendarFormat, index);
66 | final visibleRange = _getVisibleRange(calendarFormat, baseDay);
67 | final visibleDays = _daysInRange(visibleRange.start, visibleRange.end);
68 |
69 | final actualDowHeight = dowVisible ? dowHeight! : 0.0;
70 | final constrainedRowHeight = constraints.hasBoundedHeight
71 | ? (constraints.maxHeight - actualDowHeight) /
72 | _getRowCount(calendarFormat, baseDay)
73 | : null;
74 |
75 | return CalendarPage(
76 | visibleDays: visibleDays,
77 | dowVisible: dowVisible,
78 | dowDecoration: dowDecoration,
79 | rowDecoration: rowDecoration,
80 | tableBorder: tableBorder,
81 | tablePadding: tablePadding,
82 | dowBuilder: (context, day) {
83 | return SizedBox(
84 | height: dowHeight,
85 | child: dowBuilder?.call(context, day),
86 | );
87 | },
88 | dayBuilder: (context, day) {
89 | DateTime baseDay;
90 | final previousFocusedDay = focusedDay;
91 | if (previousFocusedDay == null || previousIndex == null) {
92 | baseDay = _getBaseDay(calendarFormat, index);
93 | } else {
94 | baseDay =
95 | _getFocusedDay(calendarFormat, previousFocusedDay, index);
96 | }
97 |
98 | return SizedBox(
99 | height: constrainedRowHeight ?? rowHeight,
100 | child: dayBuilder(context, day, baseDay),
101 | );
102 | },
103 | dowHeight: dowHeight,
104 | weekNumberVisible: weekNumbersVisible,
105 | weekNumberBuilder: (context, day) {
106 | return SizedBox(
107 | height: constrainedRowHeight ?? rowHeight,
108 | child: weekNumberBuilder?.call(context, day),
109 | );
110 | },
111 | );
112 | },
113 | onPageChanged: (index) {
114 | DateTime baseDay;
115 | final previousFocusedDay = focusedDay;
116 | if (previousFocusedDay == null || previousIndex == null) {
117 | baseDay = _getBaseDay(calendarFormat, index);
118 | } else {
119 | baseDay = _getFocusedDay(calendarFormat, previousFocusedDay, index);
120 | }
121 |
122 | return onPageChanged(index, baseDay);
123 | },
124 | );
125 | }
126 |
127 | int _getPageCount(CalendarFormat format, DateTime first, DateTime last) {
128 | switch (format) {
129 | case CalendarFormat.month:
130 | return _getMonthCount(first, last) + 1;
131 | case CalendarFormat.twoWeeks:
132 | return _getTwoWeekCount(first, last) + 1;
133 | case CalendarFormat.week:
134 | return _getWeekCount(first, last) + 1;
135 | }
136 | }
137 |
138 | int _getMonthCount(DateTime first, DateTime last) {
139 | final yearDif = last.year - first.year;
140 | final monthDif = last.month - first.month;
141 |
142 | return yearDif * 12 + monthDif;
143 | }
144 |
145 | int _getWeekCount(DateTime first, DateTime last) {
146 | return last.difference(_firstDayOfWeek(first)).inDays ~/ 7;
147 | }
148 |
149 | int _getTwoWeekCount(DateTime first, DateTime last) {
150 | return last.difference(_firstDayOfWeek(first)).inDays ~/ 14;
151 | }
152 |
153 | DateTime _getFocusedDay(
154 | CalendarFormat format,
155 | DateTime prevFocusedDay,
156 | int pageIndex,
157 | ) {
158 | if (pageIndex == previousIndex) {
159 | return prevFocusedDay;
160 | }
161 |
162 | final pageDif = pageIndex - previousIndex!;
163 | DateTime day;
164 |
165 | switch (format) {
166 | case CalendarFormat.month:
167 | day = DateTime.utc(prevFocusedDay.year, prevFocusedDay.month + pageDif);
168 | case CalendarFormat.twoWeeks:
169 | day = DateTime.utc(
170 | prevFocusedDay.year,
171 | prevFocusedDay.month,
172 | prevFocusedDay.day + pageDif * 14,
173 | );
174 | case CalendarFormat.week:
175 | day = DateTime.utc(
176 | prevFocusedDay.year,
177 | prevFocusedDay.month,
178 | prevFocusedDay.day + pageDif * 7,
179 | );
180 | }
181 |
182 | if (day.isBefore(firstDay)) {
183 | day = firstDay;
184 | } else if (day.isAfter(lastDay)) {
185 | day = lastDay;
186 | }
187 |
188 | return day;
189 | }
190 |
191 | DateTime _getBaseDay(CalendarFormat format, int pageIndex) {
192 | DateTime day;
193 |
194 | switch (format) {
195 | case CalendarFormat.month:
196 | day = DateTime.utc(firstDay.year, firstDay.month + pageIndex);
197 | case CalendarFormat.twoWeeks:
198 | day = DateTime.utc(
199 | firstDay.year,
200 | firstDay.month,
201 | firstDay.day + pageIndex * 14,
202 | );
203 | case CalendarFormat.week:
204 | day = DateTime.utc(
205 | firstDay.year,
206 | firstDay.month,
207 | firstDay.day + pageIndex * 7,
208 | );
209 | }
210 |
211 | if (day.isBefore(firstDay)) {
212 | day = firstDay;
213 | } else if (day.isAfter(lastDay)) {
214 | day = lastDay;
215 | }
216 |
217 | return day;
218 | }
219 |
220 | DateTimeRange _getVisibleRange(CalendarFormat format, DateTime focusedDay) {
221 | switch (format) {
222 | case CalendarFormat.month:
223 | return _daysInMonth(focusedDay);
224 | case CalendarFormat.twoWeeks:
225 | return _daysInTwoWeeks(focusedDay);
226 | case CalendarFormat.week:
227 | return _daysInWeek(focusedDay);
228 | }
229 | }
230 |
231 | DateTimeRange _daysInWeek(DateTime focusedDay) {
232 | final daysBefore = _getDaysBefore(focusedDay);
233 | final firstToDisplay = focusedDay.subtract(Duration(days: daysBefore));
234 | final lastToDisplay = firstToDisplay.add(const Duration(days: 7));
235 | return DateTimeRange(start: firstToDisplay, end: lastToDisplay);
236 | }
237 |
238 | DateTimeRange _daysInTwoWeeks(DateTime focusedDay) {
239 | final daysBefore = _getDaysBefore(focusedDay);
240 | final firstToDisplay = focusedDay.subtract(Duration(days: daysBefore));
241 | final lastToDisplay = firstToDisplay.add(const Duration(days: 14));
242 | return DateTimeRange(start: firstToDisplay, end: lastToDisplay);
243 | }
244 |
245 | DateTimeRange _daysInMonth(DateTime focusedDay) {
246 | final first = _firstDayOfMonth(focusedDay);
247 | final daysBefore = _getDaysBefore(first);
248 | final firstToDisplay = first.subtract(Duration(days: daysBefore));
249 |
250 | if (sixWeekMonthsEnforced) {
251 | final end = firstToDisplay.add(const Duration(days: 42));
252 | return DateTimeRange(start: firstToDisplay, end: end);
253 | }
254 |
255 | final last = _lastDayOfMonth(focusedDay);
256 | final daysAfter = _getDaysAfter(last);
257 | final lastToDisplay = last.add(Duration(days: daysAfter));
258 |
259 | return DateTimeRange(start: firstToDisplay, end: lastToDisplay);
260 | }
261 |
262 | List _daysInRange(DateTime first, DateTime last) {
263 | final dayCount = last.difference(first).inDays + 1;
264 | return List.generate(
265 | dayCount,
266 | (index) => DateTime.utc(first.year, first.month, first.day + index),
267 | );
268 | }
269 |
270 | DateTime _firstDayOfWeek(DateTime week) {
271 | final daysBefore = _getDaysBefore(week);
272 | return week.subtract(Duration(days: daysBefore));
273 | }
274 |
275 | DateTime _firstDayOfMonth(DateTime month) {
276 | return DateTime.utc(month.year, month.month);
277 | }
278 |
279 | DateTime _lastDayOfMonth(DateTime month) {
280 | final date = month.month < 12
281 | ? DateTime.utc(month.year, month.month + 1)
282 | : DateTime.utc(month.year + 1);
283 | return date.subtract(const Duration(days: 1));
284 | }
285 |
286 | int _getRowCount(CalendarFormat format, DateTime focusedDay) {
287 | if (format == CalendarFormat.twoWeeks) {
288 | return 2;
289 | } else if (format == CalendarFormat.week) {
290 | return 1;
291 | } else if (sixWeekMonthsEnforced) {
292 | return 6;
293 | }
294 |
295 | final first = _firstDayOfMonth(focusedDay);
296 | final daysBefore = _getDaysBefore(first);
297 | final firstToDisplay = first.subtract(Duration(days: daysBefore));
298 |
299 | final last = _lastDayOfMonth(focusedDay);
300 | final daysAfter = _getDaysAfter(last);
301 | final lastToDisplay = last.add(Duration(days: daysAfter));
302 |
303 | return (lastToDisplay.difference(firstToDisplay).inDays + 1) ~/ 7;
304 | }
305 |
306 | int _getDaysBefore(DateTime firstDay) {
307 | return (firstDay.weekday + 7 - getWeekdayNumber(startingDayOfWeek)) % 7;
308 | }
309 |
310 | int _getDaysAfter(DateTime lastDay) {
311 | final invertedStartingWeekday = 8 - getWeekdayNumber(startingDayOfWeek);
312 |
313 | final daysAfter = 7 - ((lastDay.weekday + invertedStartingWeekday) % 7);
314 | if (daysAfter == 7) {
315 | return 0;
316 | }
317 |
318 | return daysAfter;
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/lib/src/widgets/calendar_header.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/widgets.dart';
5 | import 'package:intl/intl.dart';
6 | import 'package:table_calendar/src/customization/header_style.dart';
7 | import 'package:table_calendar/src/shared/utils.dart'
8 | show CalendarFormat, DayBuilder;
9 | import 'package:table_calendar/src/widgets/custom_icon_button.dart';
10 | import 'package:table_calendar/src/widgets/format_button.dart';
11 |
12 | class CalendarHeader extends StatelessWidget {
13 | final dynamic locale;
14 | final DateTime focusedMonth;
15 | final CalendarFormat calendarFormat;
16 | final HeaderStyle headerStyle;
17 | final VoidCallback onLeftChevronTap;
18 | final VoidCallback onRightChevronTap;
19 | final VoidCallback onHeaderTap;
20 | final VoidCallback onHeaderLongPress;
21 | final ValueChanged onFormatButtonTap;
22 | final Map availableCalendarFormats;
23 | final DayBuilder? headerTitleBuilder;
24 |
25 | const CalendarHeader({
26 | super.key,
27 | this.locale,
28 | required this.focusedMonth,
29 | required this.calendarFormat,
30 | required this.headerStyle,
31 | required this.onLeftChevronTap,
32 | required this.onRightChevronTap,
33 | required this.onHeaderTap,
34 | required this.onHeaderLongPress,
35 | required this.onFormatButtonTap,
36 | required this.availableCalendarFormats,
37 | this.headerTitleBuilder,
38 | });
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | final text = headerStyle.titleTextFormatter?.call(focusedMonth, locale) ??
43 | DateFormat.yMMMM(locale).format(focusedMonth);
44 |
45 | return Container(
46 | decoration: headerStyle.decoration,
47 | margin: headerStyle.headerMargin,
48 | padding: headerStyle.headerPadding,
49 | child: Row(
50 | children: [
51 | if (headerStyle.leftChevronVisible)
52 | CustomIconButton(
53 | icon: headerStyle.leftChevronIcon,
54 | onTap: onLeftChevronTap,
55 | margin: headerStyle.leftChevronMargin,
56 | padding: headerStyle.leftChevronPadding,
57 | ),
58 | Expanded(
59 | child: headerTitleBuilder?.call(context, focusedMonth) ??
60 | GestureDetector(
61 | onTap: onHeaderTap,
62 | onLongPress: onHeaderLongPress,
63 | child: Text(
64 | text,
65 | style: headerStyle.titleTextStyle,
66 | textAlign: headerStyle.titleCentered
67 | ? TextAlign.center
68 | : TextAlign.start,
69 | ),
70 | ),
71 | ),
72 | if (headerStyle.formatButtonVisible &&
73 | availableCalendarFormats.length > 1)
74 | Padding(
75 | padding: const EdgeInsets.only(left: 8.0),
76 | child: FormatButton(
77 | onTap: onFormatButtonTap,
78 | availableCalendarFormats: availableCalendarFormats,
79 | calendarFormat: calendarFormat,
80 | decoration: headerStyle.formatButtonDecoration,
81 | padding: headerStyle.formatButtonPadding,
82 | textStyle: headerStyle.formatButtonTextStyle,
83 | showsNextFormat: headerStyle.formatButtonShowsNext,
84 | ),
85 | ),
86 | if (headerStyle.rightChevronVisible)
87 | CustomIconButton(
88 | icon: headerStyle.rightChevronIcon,
89 | onTap: onRightChevronTap,
90 | margin: headerStyle.rightChevronMargin,
91 | padding: headerStyle.rightChevronPadding,
92 | ),
93 | ],
94 | ),
95 | );
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/lib/src/widgets/calendar_page.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/widgets.dart';
5 |
6 | class CalendarPage extends StatelessWidget {
7 | final Widget Function(BuildContext context, DateTime day)? dowBuilder;
8 | final Widget Function(BuildContext context, DateTime day) dayBuilder;
9 | final Widget Function(BuildContext context, DateTime day)? weekNumberBuilder;
10 | final List visibleDays;
11 | final Decoration? dowDecoration;
12 | final Decoration? rowDecoration;
13 | final TableBorder? tableBorder;
14 | final EdgeInsets? tablePadding;
15 | final bool dowVisible;
16 | final bool weekNumberVisible;
17 | final double? dowHeight;
18 |
19 | const CalendarPage({
20 | super.key,
21 | required this.visibleDays,
22 | this.dowBuilder,
23 | required this.dayBuilder,
24 | this.weekNumberBuilder,
25 | this.dowDecoration,
26 | this.rowDecoration,
27 | this.tableBorder,
28 | this.tablePadding,
29 | this.dowVisible = true,
30 | this.weekNumberVisible = false,
31 | this.dowHeight,
32 | }) : assert(!dowVisible || (dowHeight != null && dowBuilder != null)),
33 | assert(!weekNumberVisible || weekNumberBuilder != null);
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return Padding(
38 | padding: tablePadding ?? EdgeInsets.zero,
39 | child: Row(
40 | crossAxisAlignment: CrossAxisAlignment.stretch,
41 | children: [
42 | if (weekNumberVisible) _buildWeekNumbers(context),
43 | Expanded(
44 | child: Table(
45 | border: tableBorder,
46 | children: [
47 | if (dowVisible) _buildDaysOfWeek(context),
48 | ..._buildCalendarDays(context),
49 | ],
50 | ),
51 | ),
52 | ],
53 | ),
54 | );
55 | }
56 |
57 | Widget _buildWeekNumbers(BuildContext context) {
58 | final rowAmount = visibleDays.length ~/ 7;
59 |
60 | return Column(
61 | children: [
62 | if (dowVisible) SizedBox(height: dowHeight ?? 0),
63 | ...List.generate(
64 | rowAmount,
65 | (index) => Expanded(
66 | child: weekNumberBuilder!(context, visibleDays[index * 7]),
67 | ),
68 | ),
69 | ],
70 | );
71 | }
72 |
73 | TableRow _buildDaysOfWeek(BuildContext context) {
74 | return TableRow(
75 | decoration: dowDecoration,
76 | children: List.generate(
77 | 7,
78 | (index) => dowBuilder!(context, visibleDays[index]),
79 | ),
80 | );
81 | }
82 |
83 | List _buildCalendarDays(BuildContext context) {
84 | final rowAmount = visibleDays.length ~/ 7;
85 |
86 | return List.generate(
87 | rowAmount,
88 | (index) => TableRow(
89 | decoration: rowDecoration,
90 | children: List.generate(
91 | 7,
92 | (id) => dayBuilder(context, visibleDays[index * 7 + id]),
93 | ),
94 | ),
95 | );
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/lib/src/widgets/cell_content.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/widgets.dart';
5 | import 'package:intl/intl.dart';
6 | import 'package:table_calendar/src/customization/calendar_builders.dart';
7 | import 'package:table_calendar/src/customization/calendar_style.dart';
8 |
9 | class CellContent extends StatelessWidget {
10 | final DateTime day;
11 | final DateTime focusedDay;
12 | final dynamic locale;
13 | final bool isTodayHighlighted;
14 | final bool isToday;
15 | final bool isSelected;
16 | final bool isRangeStart;
17 | final bool isRangeEnd;
18 | final bool isWithinRange;
19 | final bool isOutside;
20 | final bool isDisabled;
21 | final bool isHoliday;
22 | final bool isWeekend;
23 | final CalendarStyle calendarStyle;
24 | final CalendarBuilders calendarBuilders;
25 |
26 | const CellContent({
27 | super.key,
28 | required this.day,
29 | required this.focusedDay,
30 | required this.calendarStyle,
31 | required this.calendarBuilders,
32 | required this.isTodayHighlighted,
33 | required this.isToday,
34 | required this.isSelected,
35 | required this.isRangeStart,
36 | required this.isRangeEnd,
37 | required this.isWithinRange,
38 | required this.isOutside,
39 | required this.isDisabled,
40 | required this.isHoliday,
41 | required this.isWeekend,
42 | this.locale,
43 | });
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | final dowLabel = DateFormat.EEEE(locale).format(day);
48 | final dayLabel = DateFormat.yMMMMd(locale).format(day);
49 | final semanticsLabel = '$dowLabel, $dayLabel';
50 |
51 | Widget? cell =
52 | calendarBuilders.prioritizedBuilder?.call(context, day, focusedDay);
53 |
54 | if (cell != null) {
55 | return Semantics(
56 | label: semanticsLabel,
57 | excludeSemantics: true,
58 | child: cell,
59 | );
60 | }
61 |
62 | final text =
63 | calendarStyle.dayTextFormatter?.call(day, locale) ?? '${day.day}';
64 | final margin = calendarStyle.cellMargin;
65 | final padding = calendarStyle.cellPadding;
66 | final alignment = calendarStyle.cellAlignment;
67 | const duration = Duration(milliseconds: 250);
68 |
69 | if (isDisabled) {
70 | cell = calendarBuilders.disabledBuilder?.call(context, day, focusedDay) ??
71 | AnimatedContainer(
72 | duration: duration,
73 | margin: margin,
74 | padding: padding,
75 | decoration: calendarStyle.disabledDecoration,
76 | alignment: alignment,
77 | child: Text(text, style: calendarStyle.disabledTextStyle),
78 | );
79 | } else if (isSelected) {
80 | cell = calendarBuilders.selectedBuilder?.call(context, day, focusedDay) ??
81 | AnimatedContainer(
82 | duration: duration,
83 | margin: margin,
84 | padding: padding,
85 | decoration: calendarStyle.selectedDecoration,
86 | alignment: alignment,
87 | child: Text(text, style: calendarStyle.selectedTextStyle),
88 | );
89 | } else if (isRangeStart) {
90 | cell =
91 | calendarBuilders.rangeStartBuilder?.call(context, day, focusedDay) ??
92 | AnimatedContainer(
93 | duration: duration,
94 | margin: margin,
95 | padding: padding,
96 | decoration: calendarStyle.rangeStartDecoration,
97 | alignment: alignment,
98 | child: Text(text, style: calendarStyle.rangeStartTextStyle),
99 | );
100 | } else if (isRangeEnd) {
101 | cell = calendarBuilders.rangeEndBuilder?.call(context, day, focusedDay) ??
102 | AnimatedContainer(
103 | duration: duration,
104 | margin: margin,
105 | padding: padding,
106 | decoration: calendarStyle.rangeEndDecoration,
107 | alignment: alignment,
108 | child: Text(text, style: calendarStyle.rangeEndTextStyle),
109 | );
110 | } else if (isToday && isTodayHighlighted) {
111 | cell = calendarBuilders.todayBuilder?.call(context, day, focusedDay) ??
112 | AnimatedContainer(
113 | duration: duration,
114 | margin: margin,
115 | padding: padding,
116 | decoration: calendarStyle.todayDecoration,
117 | alignment: alignment,
118 | child: Text(text, style: calendarStyle.todayTextStyle),
119 | );
120 | } else if (isHoliday) {
121 | cell = calendarBuilders.holidayBuilder?.call(context, day, focusedDay) ??
122 | AnimatedContainer(
123 | duration: duration,
124 | margin: margin,
125 | padding: padding,
126 | decoration: calendarStyle.holidayDecoration,
127 | alignment: alignment,
128 | child: Text(text, style: calendarStyle.holidayTextStyle),
129 | );
130 | } else if (isWithinRange) {
131 | cell =
132 | calendarBuilders.withinRangeBuilder?.call(context, day, focusedDay) ??
133 | AnimatedContainer(
134 | duration: duration,
135 | margin: margin,
136 | padding: padding,
137 | decoration: calendarStyle.withinRangeDecoration,
138 | alignment: alignment,
139 | child: Text(text, style: calendarStyle.withinRangeTextStyle),
140 | );
141 | } else if (isOutside) {
142 | cell = calendarBuilders.outsideBuilder?.call(context, day, focusedDay) ??
143 | AnimatedContainer(
144 | duration: duration,
145 | margin: margin,
146 | padding: padding,
147 | decoration: calendarStyle.outsideDecoration,
148 | alignment: alignment,
149 | child: Text(text, style: calendarStyle.outsideTextStyle),
150 | );
151 | } else {
152 | cell = calendarBuilders.defaultBuilder?.call(context, day, focusedDay) ??
153 | AnimatedContainer(
154 | duration: duration,
155 | margin: margin,
156 | padding: padding,
157 | decoration: isWeekend
158 | ? calendarStyle.weekendDecoration
159 | : calendarStyle.defaultDecoration,
160 | alignment: alignment,
161 | child: Text(
162 | text,
163 | style: isWeekend
164 | ? calendarStyle.weekendTextStyle
165 | : calendarStyle.defaultTextStyle,
166 | ),
167 | );
168 | }
169 |
170 | return Semantics(
171 | label: semanticsLabel,
172 | excludeSemantics: true,
173 | child: cell,
174 | );
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/lib/src/widgets/custom_icon_button.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/cupertino.dart';
5 | import 'package:flutter/foundation.dart';
6 | import 'package:flutter/material.dart';
7 |
8 | class CustomIconButton extends StatelessWidget {
9 | final Widget icon;
10 | final VoidCallback onTap;
11 | final EdgeInsets margin;
12 | final EdgeInsets padding;
13 |
14 | const CustomIconButton({
15 | super.key,
16 | required this.icon,
17 | required this.onTap,
18 | this.margin = EdgeInsets.zero,
19 | this.padding = const EdgeInsets.all(8.0),
20 | });
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | final platform = Theme.of(context).platform;
25 |
26 | return Padding(
27 | padding: margin,
28 | child: !kIsWeb &&
29 | (platform == TargetPlatform.iOS ||
30 | platform == TargetPlatform.macOS)
31 | ? CupertinoButton(
32 | onPressed: onTap,
33 | padding: padding,
34 | child: icon,
35 | )
36 | : InkWell(
37 | onTap: onTap,
38 | borderRadius: BorderRadius.circular(100.0),
39 | child: Padding(
40 | padding: padding,
41 | child: icon,
42 | ),
43 | ),
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/src/widgets/format_button.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/cupertino.dart';
5 | import 'package:flutter/foundation.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:table_calendar/src/shared/utils.dart' show CalendarFormat;
8 |
9 | class FormatButton extends StatelessWidget {
10 | final CalendarFormat calendarFormat;
11 | final ValueChanged onTap;
12 | final TextStyle textStyle;
13 | final BoxDecoration decoration;
14 | final EdgeInsets padding;
15 | final bool showsNextFormat;
16 | final Map availableCalendarFormats;
17 |
18 | const FormatButton({
19 | super.key,
20 | required this.calendarFormat,
21 | required this.onTap,
22 | required this.textStyle,
23 | required this.decoration,
24 | required this.padding,
25 | required this.showsNextFormat,
26 | required this.availableCalendarFormats,
27 | });
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | final child = Container(
32 | decoration: decoration,
33 | padding: padding,
34 | child: Text(
35 | _formatButtonText,
36 | style: textStyle,
37 | ),
38 | );
39 |
40 | final platform = Theme.of(context).platform;
41 |
42 | return !kIsWeb &&
43 | (platform == TargetPlatform.iOS || platform == TargetPlatform.macOS)
44 | ? CupertinoButton(
45 | onPressed: () => onTap(_nextFormat()),
46 | padding: EdgeInsets.zero,
47 | child: child,
48 | )
49 | : InkWell(
50 | borderRadius:
51 | decoration.borderRadius?.resolve(Directionality.of(context)),
52 | onTap: () => onTap(_nextFormat()),
53 | child: child,
54 | );
55 | }
56 |
57 | String get _formatButtonText => showsNextFormat
58 | ? availableCalendarFormats[_nextFormat()]!
59 | : availableCalendarFormats[calendarFormat]!;
60 |
61 | CalendarFormat _nextFormat() {
62 | final formats = availableCalendarFormats.keys.toList();
63 | int id = formats.indexOf(calendarFormat);
64 | id = (id + 1) % formats.length;
65 |
66 | return formats[id];
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/table_calendar.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | export 'src/customization/calendar_builders.dart';
5 | export 'src/customization/calendar_style.dart';
6 | export 'src/customization/days_of_week_style.dart';
7 | export 'src/customization/header_style.dart';
8 | export 'src/shared/utils.dart';
9 | export 'src/table_calendar.dart';
10 | export 'src/table_calendar_base.dart';
11 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.11.0"
12 | boolean_selector:
13 | dependency: transitive
14 | description:
15 | name: boolean_selector
16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "2.1.1"
20 | characters:
21 | dependency: transitive
22 | description:
23 | name: characters
24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "1.3.0"
28 | clock:
29 | dependency: transitive
30 | description:
31 | name: clock
32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.1.1"
36 | collection:
37 | dependency: transitive
38 | description:
39 | name: collection
40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.18.0"
44 | fake_async:
45 | dependency: transitive
46 | description:
47 | name: fake_async
48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.3.1"
52 | flutter:
53 | dependency: "direct main"
54 | description: flutter
55 | source: sdk
56 | version: "0.0.0"
57 | flutter_test:
58 | dependency: "direct dev"
59 | description: flutter
60 | source: sdk
61 | version: "0.0.0"
62 | http:
63 | dependency: transitive
64 | description:
65 | name: http
66 | sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
67 | url: "https://pub.dev"
68 | source: hosted
69 | version: "1.2.2"
70 | http_parser:
71 | dependency: transitive
72 | description:
73 | name: http_parser
74 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
75 | url: "https://pub.dev"
76 | source: hosted
77 | version: "4.0.2"
78 | intl:
79 | dependency: "direct main"
80 | description:
81 | name: intl
82 | sha256: "00f33b908655e606b86d2ade4710a231b802eec6f11e87e4ea3783fd72077a50"
83 | url: "https://pub.dev"
84 | source: hosted
85 | version: "0.20.1"
86 | leak_tracker:
87 | dependency: transitive
88 | description:
89 | name: leak_tracker
90 | sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
91 | url: "https://pub.dev"
92 | source: hosted
93 | version: "10.0.5"
94 | leak_tracker_flutter_testing:
95 | dependency: transitive
96 | description:
97 | name: leak_tracker_flutter_testing
98 | sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
99 | url: "https://pub.dev"
100 | source: hosted
101 | version: "3.0.5"
102 | leak_tracker_testing:
103 | dependency: transitive
104 | description:
105 | name: leak_tracker_testing
106 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
107 | url: "https://pub.dev"
108 | source: hosted
109 | version: "3.0.1"
110 | lint:
111 | dependency: "direct dev"
112 | description:
113 | name: lint
114 | sha256: d758a5211fce7fd3f5e316f804daefecdc34c7e53559716125e6da7388ae8565
115 | url: "https://pub.dev"
116 | source: hosted
117 | version: "2.3.0"
118 | matcher:
119 | dependency: transitive
120 | description:
121 | name: matcher
122 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
123 | url: "https://pub.dev"
124 | source: hosted
125 | version: "0.12.16+1"
126 | material_color_utilities:
127 | dependency: transitive
128 | description:
129 | name: material_color_utilities
130 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
131 | url: "https://pub.dev"
132 | source: hosted
133 | version: "0.11.1"
134 | meta:
135 | dependency: transitive
136 | description:
137 | name: meta
138 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
139 | url: "https://pub.dev"
140 | source: hosted
141 | version: "1.15.0"
142 | path:
143 | dependency: transitive
144 | description:
145 | name: path
146 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
147 | url: "https://pub.dev"
148 | source: hosted
149 | version: "1.9.0"
150 | simple_gesture_detector:
151 | dependency: "direct main"
152 | description:
153 | name: simple_gesture_detector
154 | sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
155 | url: "https://pub.dev"
156 | source: hosted
157 | version: "0.2.1"
158 | sky_engine:
159 | dependency: transitive
160 | description: flutter
161 | source: sdk
162 | version: "0.0.99"
163 | source_span:
164 | dependency: transitive
165 | description:
166 | name: source_span
167 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
168 | url: "https://pub.dev"
169 | source: hosted
170 | version: "1.10.0"
171 | stack_trace:
172 | dependency: transitive
173 | description:
174 | name: stack_trace
175 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
176 | url: "https://pub.dev"
177 | source: hosted
178 | version: "1.11.1"
179 | stream_channel:
180 | dependency: transitive
181 | description:
182 | name: stream_channel
183 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
184 | url: "https://pub.dev"
185 | source: hosted
186 | version: "2.1.2"
187 | string_scanner:
188 | dependency: transitive
189 | description:
190 | name: string_scanner
191 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
192 | url: "https://pub.dev"
193 | source: hosted
194 | version: "1.2.0"
195 | term_glyph:
196 | dependency: transitive
197 | description:
198 | name: term_glyph
199 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
200 | url: "https://pub.dev"
201 | source: hosted
202 | version: "1.2.1"
203 | test_api:
204 | dependency: transitive
205 | description:
206 | name: test_api
207 | sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
208 | url: "https://pub.dev"
209 | source: hosted
210 | version: "0.7.2"
211 | typed_data:
212 | dependency: transitive
213 | description:
214 | name: typed_data
215 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
216 | url: "https://pub.dev"
217 | source: hosted
218 | version: "1.4.0"
219 | vector_math:
220 | dependency: transitive
221 | description:
222 | name: vector_math
223 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
224 | url: "https://pub.dev"
225 | source: hosted
226 | version: "2.1.4"
227 | vm_service:
228 | dependency: transitive
229 | description:
230 | name: vm_service
231 | sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
232 | url: "https://pub.dev"
233 | source: hosted
234 | version: "14.2.5"
235 | web:
236 | dependency: transitive
237 | description:
238 | name: web
239 | sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
240 | url: "https://pub.dev"
241 | source: hosted
242 | version: "1.1.0"
243 | sdks:
244 | dart: ">=3.5.0 <4.0.0"
245 | flutter: ">=3.18.0-18.0.pre.54"
246 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: table_calendar
2 | description: Highly customizable, feature-packed calendar widget for Flutter.
3 | version: 3.2.0
4 | author: Aleksander Woźniak
5 | homepage: https://github.com/aleksanderwozniak/table_calendar
6 |
7 | environment:
8 | sdk: ">=3.0.0 <4.0.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | intl: ^0.20.0
15 | simple_gesture_detector: ^0.2.0
16 |
17 | dev_dependencies:
18 | flutter_test:
19 | sdk: flutter
20 | lint: ^2.0.1
21 |
22 | flutter:
23 |
--------------------------------------------------------------------------------
/test/calendar_header_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_test/flutter_test.dart';
6 | import 'package:intl/intl.dart' as intl;
7 | import 'package:table_calendar/src/customization/header_style.dart';
8 | import 'package:table_calendar/src/shared/utils.dart';
9 | import 'package:table_calendar/src/widgets/calendar_header.dart';
10 | import 'package:table_calendar/src/widgets/custom_icon_button.dart';
11 | import 'package:table_calendar/src/widgets/format_button.dart';
12 |
13 | import 'common.dart';
14 |
15 | final focusedMonth = DateTime.utc(2021, 7, 15);
16 |
17 | Widget setupTestWidget({
18 | HeaderStyle headerStyle = const HeaderStyle(),
19 | VoidCallback? onLeftChevronTap,
20 | VoidCallback? onRightChevronTap,
21 | VoidCallback? onHeaderTap,
22 | VoidCallback? onHeaderLongPress,
23 | Function(CalendarFormat)? onFormatButtonTap,
24 | Map availableCalendarFormats = calendarFormatMap,
25 | }) {
26 | return Directionality(
27 | textDirection: TextDirection.ltr,
28 | child: Material(
29 | child: CalendarHeader(
30 | focusedMonth: focusedMonth,
31 | calendarFormat: CalendarFormat.month,
32 | headerStyle: headerStyle,
33 | onLeftChevronTap: () => onLeftChevronTap?.call(),
34 | onRightChevronTap: () => onRightChevronTap?.call(),
35 | onHeaderTap: () => onHeaderTap?.call(),
36 | onHeaderLongPress: () => onHeaderLongPress?.call(),
37 | onFormatButtonTap: (format) => onFormatButtonTap?.call(format),
38 | availableCalendarFormats: availableCalendarFormats,
39 | ),
40 | ),
41 | );
42 | }
43 |
44 | void main() {
45 | testWidgets(
46 | 'Displays corrent month and year for given focusedMonth',
47 | (tester) async {
48 | await tester.pumpWidget(setupTestWidget());
49 |
50 | final headerText = intl.DateFormat.yMMMM().format(focusedMonth);
51 |
52 | expect(find.byType(CalendarHeader), findsOneWidget);
53 | expect(find.text(headerText), findsOneWidget);
54 | },
55 | );
56 | testWidgets(
57 | 'Ensure chevrons and FormatButton are visible by default, test onTap callbacks',
58 | (tester) async {
59 | bool leftChevronTapped = false;
60 | bool rightChevronTapped = false;
61 | bool headerTapped = false;
62 | bool headerLongPressed = false;
63 | bool formatButtonTapped = false;
64 |
65 | await tester.pumpWidget(
66 | setupTestWidget(
67 | onLeftChevronTap: () => leftChevronTapped = true,
68 | onRightChevronTap: () => rightChevronTapped = true,
69 | onHeaderTap: () => headerTapped = true,
70 | onHeaderLongPress: () => headerLongPressed = true,
71 | onFormatButtonTap: (_) => formatButtonTapped = true,
72 | ),
73 | );
74 |
75 | final leftChevron = find.widgetWithIcon(
76 | CustomIconButton,
77 | Icons.chevron_left,
78 | );
79 |
80 | final rightChevron = find.widgetWithIcon(
81 | CustomIconButton,
82 | Icons.chevron_right,
83 | );
84 |
85 | final header = find.byType(CalendarHeader);
86 | final formatButton = find.byType(FormatButton);
87 |
88 | expect(leftChevron, findsOneWidget);
89 | expect(rightChevron, findsOneWidget);
90 | expect(header, findsOneWidget);
91 | expect(formatButton, findsOneWidget);
92 |
93 | expect(leftChevronTapped, false);
94 | expect(rightChevronTapped, false);
95 | expect(headerTapped, false);
96 | expect(headerLongPressed, false);
97 | expect(formatButtonTapped, false);
98 |
99 | await tester.tap(leftChevron);
100 | await tester.pumpAndSettle();
101 |
102 | await tester.tap(rightChevron);
103 | await tester.pumpAndSettle();
104 |
105 | await tester.tap(header);
106 | await tester.pumpAndSettle();
107 |
108 | await tester.longPress(header);
109 | await tester.pumpAndSettle();
110 |
111 | await tester.tap(formatButton);
112 | await tester.pumpAndSettle();
113 |
114 | expect(leftChevronTapped, true);
115 | expect(rightChevronTapped, true);
116 | expect(headerTapped, true);
117 | expect(headerLongPressed, true);
118 | expect(formatButtonTapped, true);
119 | },
120 | );
121 |
122 | testWidgets(
123 | 'When leftChevronVisible is false, do not show the left chevron',
124 | (tester) async {
125 | await tester.pumpWidget(
126 | setupTestWidget(
127 | headerStyle: const HeaderStyle(
128 | leftChevronVisible: false,
129 | ),
130 | ),
131 | );
132 |
133 | final leftChevron = find.widgetWithIcon(
134 | CustomIconButton,
135 | Icons.chevron_left,
136 | );
137 |
138 | final rightChevron = find.widgetWithIcon(
139 | CustomIconButton,
140 | Icons.chevron_right,
141 | );
142 |
143 | expect(leftChevron, findsNothing);
144 | expect(rightChevron, findsOneWidget);
145 | },
146 | );
147 |
148 | testWidgets(
149 | 'When rightChevronVisible is false, do not show the right chevron',
150 | (tester) async {
151 | await tester.pumpWidget(
152 | setupTestWidget(
153 | headerStyle: const HeaderStyle(
154 | rightChevronVisible: false,
155 | ),
156 | ),
157 | );
158 |
159 | final leftChevron = find.widgetWithIcon(
160 | CustomIconButton,
161 | Icons.chevron_left,
162 | );
163 |
164 | final rightChevron = find.widgetWithIcon(
165 | CustomIconButton,
166 | Icons.chevron_right,
167 | );
168 |
169 | expect(leftChevron, findsOneWidget);
170 | expect(rightChevron, findsNothing);
171 | },
172 | );
173 |
174 | testWidgets(
175 | 'When availableCalendarFormats has a single format, do not show the FormatButton',
176 | (tester) async {
177 | await tester.pumpWidget(
178 | setupTestWidget(
179 | availableCalendarFormats: const {CalendarFormat.month: 'Month'},
180 | ),
181 | );
182 |
183 | final formatButton = find.byType(FormatButton);
184 | expect(formatButton, findsNothing);
185 | },
186 | );
187 |
188 | testWidgets(
189 | 'When formatButtonVisible is false, do not show the FormatButton',
190 | (tester) async {
191 | await tester.pumpWidget(
192 | setupTestWidget(
193 | headerStyle: const HeaderStyle(formatButtonVisible: false),
194 | ),
195 | );
196 |
197 | final formatButton = find.byType(FormatButton);
198 | expect(formatButton, findsNothing);
199 | },
200 | );
201 | }
202 |
--------------------------------------------------------------------------------
/test/calendar_page_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/widgets.dart';
6 | import 'package:flutter_test/flutter_test.dart';
7 | import 'package:table_calendar/src/widgets/calendar_page.dart';
8 |
9 | Widget setupTestWidget(Widget child) {
10 | return Directionality(
11 | textDirection: TextDirection.ltr,
12 | child: child,
13 | );
14 | }
15 |
16 | List visibleDays = getDaysInRange(
17 | DateTime.utc(2021, 6, 27),
18 | DateTime.utc(2021, 7, 31),
19 | );
20 |
21 | List getDaysInRange(DateTime first, DateTime last) {
22 | final dayCount = last.difference(first).inDays + 1;
23 | return List.generate(
24 | dayCount,
25 | (index) => DateTime.utc(first.year, first.month, first.day + index),
26 | );
27 | }
28 |
29 | void main() {
30 | testWidgets(
31 | 'CalendarPage lays out all the visible days',
32 | (tester) async {
33 | await tester.pumpWidget(
34 | setupTestWidget(
35 | CalendarPage(
36 | visibleDays: visibleDays,
37 | dayBuilder: (context, day) {
38 | return Text('${day.day}');
39 | },
40 | dowVisible: false,
41 | ),
42 | ),
43 | );
44 |
45 | final expectedCellCount = visibleDays.length;
46 | expect(find.byType(Text), findsNWidgets(expectedCellCount));
47 | },
48 | );
49 |
50 | testWidgets(
51 | 'CalendarPage lays out 7 DOW labels',
52 | (tester) async {
53 | await tester.pumpWidget(
54 | setupTestWidget(
55 | CalendarPage(
56 | visibleDays: visibleDays,
57 | dayBuilder: (context, day) {
58 | return Text('${day.day}');
59 | },
60 | dowBuilder: (context, day) {
61 | return Text('${day.weekday}');
62 | },
63 | dowHeight: 5,
64 | ),
65 | ),
66 | );
67 |
68 | final expectedCellCount = visibleDays.length;
69 | const expectedDowLabels = 7;
70 |
71 | expect(
72 | find.byType(Text),
73 | findsNWidgets(expectedCellCount + expectedDowLabels),
74 | );
75 | },
76 | );
77 |
78 | testWidgets(
79 | 'Throw AssertionError when CalendarPage is built with dowVisible set to true, but dowBuilder is absent',
80 | (tester) async {
81 | expect(
82 | () async {
83 | await tester.pumpWidget(
84 | setupTestWidget(
85 | CalendarPage(
86 | visibleDays: visibleDays,
87 | dayBuilder: (context, day) {
88 | return Text('${day.day}');
89 | },
90 | ),
91 | ),
92 | );
93 | },
94 | throwsAssertionError,
95 | );
96 | },
97 | );
98 |
99 | testWidgets(
100 | 'Week numbers are not visible by default',
101 | (tester) async {
102 | await tester.pumpWidget(
103 | setupTestWidget(
104 | CalendarPage(
105 | visibleDays: visibleDays,
106 | dayBuilder: (context, day) {
107 | return Text('${day.day}');
108 | },
109 | dowBuilder: (context, day) {
110 | return Text('${day.weekday}');
111 | },
112 | dowHeight: 5,
113 | ),
114 | ),
115 | );
116 |
117 | expect(
118 | find.byType(Column),
119 | findsNWidgets(0),
120 | );
121 | },
122 | );
123 |
124 | testWidgets(
125 | 'Week numbers are visible',
126 | (tester) async {
127 | await tester.pumpWidget(
128 | setupTestWidget(
129 | CalendarPage(
130 | visibleDays: visibleDays,
131 | dayBuilder: (context, day) {
132 | return Text('${day.day}');
133 | },
134 | dowBuilder: (context, day) {
135 | return Text('${day.weekday}');
136 | },
137 | dowHeight: 5,
138 | weekNumberVisible: true,
139 | weekNumberBuilder: (BuildContext context, DateTime day) {
140 | return Text(day.weekday.toString());
141 | },
142 | ),
143 | ),
144 | );
145 |
146 | expect(
147 | find.byType(Column),
148 | findsNWidgets(1),
149 | );
150 | },
151 | );
152 | }
153 |
--------------------------------------------------------------------------------
/test/cell_content_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/widgets.dart';
5 | import 'package:flutter_test/flutter_test.dart';
6 | import 'package:intl/date_symbol_data_local.dart';
7 | import 'package:intl/intl.dart' hide TextDirection;
8 | import 'package:table_calendar/src/widgets/cell_content.dart';
9 | import 'package:table_calendar/table_calendar.dart';
10 |
11 | Widget setupTestWidget(
12 | DateTime cellDay, {
13 | CalendarBuilders calendarBuilders = const CalendarBuilders(),
14 | CalendarStyle calendarStyle = const CalendarStyle(),
15 | bool isDisabled = false,
16 | bool isToday = false,
17 | bool isWeekend = false,
18 | bool isOutside = false,
19 | bool isSelected = false,
20 | bool isRangeStart = false,
21 | bool isRangeEnd = false,
22 | bool isWithinRange = false,
23 | bool isHoliday = false,
24 | bool isTodayHighlighted = true,
25 | String? locale,
26 | }) {
27 | return Directionality(
28 | textDirection: TextDirection.ltr,
29 | child: CellContent(
30 | day: cellDay,
31 | focusedDay: cellDay,
32 | calendarBuilders: calendarBuilders,
33 | calendarStyle: calendarStyle,
34 | isDisabled: isDisabled,
35 | isToday: isToday,
36 | isWeekend: isWeekend,
37 | isOutside: isOutside,
38 | isSelected: isSelected,
39 | isRangeStart: isRangeStart,
40 | isRangeEnd: isRangeEnd,
41 | isWithinRange: isWithinRange,
42 | isHoliday: isHoliday,
43 | isTodayHighlighted: isTodayHighlighted,
44 | locale: locale,
45 | ),
46 | );
47 | }
48 |
49 | void main() {
50 | group('CalendarBuilders flag test:', () {
51 | testWidgets('selectedBuilder', (tester) async {
52 | DateTime? builderDay;
53 |
54 | final calendarBuilders = CalendarBuilders(
55 | selectedBuilder: (context, day, focusedDay) {
56 | builderDay = day;
57 | return Text('${day.day}');
58 | },
59 | );
60 |
61 | final cellDay = DateTime.utc(2021, 7, 15);
62 | expect(builderDay, isNull);
63 |
64 | await tester.pumpWidget(
65 | setupTestWidget(
66 | cellDay,
67 | calendarBuilders: calendarBuilders,
68 | isSelected: true,
69 | ),
70 | );
71 |
72 | expect(builderDay, cellDay);
73 | });
74 |
75 | testWidgets('rangeStartBuilder', (tester) async {
76 | DateTime? builderDay;
77 |
78 | final calendarBuilders = CalendarBuilders(
79 | rangeStartBuilder: (context, day, focusedDay) {
80 | builderDay = day;
81 | return Text('${day.day}');
82 | },
83 | );
84 |
85 | final cellDay = DateTime.utc(2021, 7, 15);
86 | expect(builderDay, isNull);
87 |
88 | await tester.pumpWidget(
89 | setupTestWidget(
90 | cellDay,
91 | calendarBuilders: calendarBuilders,
92 | isRangeStart: true,
93 | ),
94 | );
95 |
96 | expect(builderDay, cellDay);
97 | });
98 |
99 | testWidgets('rangeEndBuilder', (tester) async {
100 | DateTime? builderDay;
101 |
102 | final calendarBuilders = CalendarBuilders(
103 | rangeEndBuilder: (context, day, focusedDay) {
104 | builderDay = day;
105 | return Text('${day.day}');
106 | },
107 | );
108 |
109 | final cellDay = DateTime.utc(2021, 7, 15);
110 | expect(builderDay, isNull);
111 |
112 | await tester.pumpWidget(
113 | setupTestWidget(
114 | cellDay,
115 | calendarBuilders: calendarBuilders,
116 | isRangeEnd: true,
117 | ),
118 | );
119 |
120 | expect(builderDay, cellDay);
121 | });
122 |
123 | testWidgets('withinRangeBuilder', (tester) async {
124 | DateTime? builderDay;
125 |
126 | final calendarBuilders = CalendarBuilders(
127 | withinRangeBuilder: (context, day, focusedDay) {
128 | builderDay = day;
129 | return Text('${day.day}');
130 | },
131 | );
132 |
133 | final cellDay = DateTime.utc(2021, 7, 15);
134 | expect(builderDay, isNull);
135 |
136 | await tester.pumpWidget(
137 | setupTestWidget(
138 | cellDay,
139 | calendarBuilders: calendarBuilders,
140 | isWithinRange: true,
141 | ),
142 | );
143 |
144 | expect(builderDay, cellDay);
145 | });
146 |
147 | testWidgets('todayBuilder', (tester) async {
148 | DateTime? builderDay;
149 |
150 | final calendarBuilders = CalendarBuilders(
151 | todayBuilder: (context, day, focusedDay) {
152 | builderDay = day;
153 | return Text('${day.day}');
154 | },
155 | );
156 |
157 | final cellDay = DateTime.utc(2021, 7, 15);
158 | expect(builderDay, isNull);
159 |
160 | await tester.pumpWidget(
161 | setupTestWidget(
162 | cellDay,
163 | calendarBuilders: calendarBuilders,
164 | isToday: true,
165 | ),
166 | );
167 |
168 | expect(builderDay, cellDay);
169 | });
170 |
171 | testWidgets('holidayBuilder', (tester) async {
172 | DateTime? builderDay;
173 |
174 | final calendarBuilders = CalendarBuilders(
175 | holidayBuilder: (context, day, focusedDay) {
176 | builderDay = day;
177 | return Text('${day.day}');
178 | },
179 | );
180 |
181 | final cellDay = DateTime.utc(2021, 7, 15);
182 | expect(builderDay, isNull);
183 |
184 | await tester.pumpWidget(
185 | setupTestWidget(
186 | cellDay,
187 | calendarBuilders: calendarBuilders,
188 | isHoliday: true,
189 | ),
190 | );
191 |
192 | expect(builderDay, cellDay);
193 | });
194 |
195 | testWidgets('outsideBuilder', (tester) async {
196 | DateTime? builderDay;
197 |
198 | final calendarBuilders = CalendarBuilders(
199 | outsideBuilder: (context, day, focusedDay) {
200 | builderDay = day;
201 | return Text('${day.day}');
202 | },
203 | );
204 |
205 | final cellDay = DateTime.utc(2021, 7, 15);
206 | expect(builderDay, isNull);
207 |
208 | await tester.pumpWidget(
209 | setupTestWidget(
210 | cellDay,
211 | calendarBuilders: calendarBuilders,
212 | isOutside: true,
213 | ),
214 | );
215 |
216 | expect(builderDay, cellDay);
217 | });
218 |
219 | testWidgets(
220 | 'defaultBuilder gets triggered when no other flags are active',
221 | (tester) async {
222 | DateTime? builderDay;
223 |
224 | final calendarBuilders = CalendarBuilders(
225 | defaultBuilder: (context, day, focusedDay) {
226 | builderDay = day;
227 | return Text('${day.day}');
228 | },
229 | );
230 |
231 | final cellDay = DateTime.utc(2021, 7, 15);
232 | expect(builderDay, isNull);
233 |
234 | await tester.pumpWidget(
235 | setupTestWidget(
236 | cellDay,
237 | calendarBuilders: calendarBuilders,
238 | ),
239 | );
240 |
241 | expect(builderDay, cellDay);
242 | },
243 | );
244 |
245 | testWidgets(
246 | 'disabledBuilder has higher build order priority than selectedBuilder',
247 | (tester) async {
248 | DateTime? builderDay;
249 | String builderName = '';
250 |
251 | final calendarBuilders = CalendarBuilders(
252 | selectedBuilder: (context, day, focusedDay) {
253 | builderName = 'selectedBuilder';
254 | builderDay = day;
255 | return Text('${day.day}');
256 | },
257 | disabledBuilder: (context, day, focusedDay) {
258 | builderName = 'disabledBuilder';
259 | builderDay = day;
260 | return Text('${day.day}');
261 | },
262 | );
263 |
264 | final cellDay = DateTime.utc(2021, 7, 15);
265 | expect(builderDay, isNull);
266 |
267 | await tester.pumpWidget(
268 | setupTestWidget(
269 | cellDay,
270 | calendarBuilders: calendarBuilders,
271 | isDisabled: true,
272 | isSelected: true,
273 | ),
274 | );
275 |
276 | expect(builderDay, cellDay);
277 | expect(builderName, 'disabledBuilder');
278 | },
279 | );
280 |
281 | testWidgets(
282 | 'prioritizedBuilder has the highest build order priority',
283 | (tester) async {
284 | DateTime? builderDay;
285 | String builderName = '';
286 |
287 | final calendarBuilders = CalendarBuilders(
288 | prioritizedBuilder: (context, day, focusedDay) {
289 | builderName = 'prioritizedBuilder';
290 | builderDay = day;
291 | return Text('${day.day}');
292 | },
293 | disabledBuilder: (context, day, focusedDay) {
294 | builderName = 'disabledBuilder';
295 | builderDay = day;
296 | return Text('${day.day}');
297 | },
298 | );
299 |
300 | final cellDay = DateTime.utc(2021, 7, 15);
301 | expect(builderDay, isNull);
302 |
303 | await tester.pumpWidget(
304 | setupTestWidget(
305 | cellDay,
306 | calendarBuilders: calendarBuilders,
307 | isDisabled: true,
308 | ),
309 | );
310 |
311 | expect(builderDay, cellDay);
312 | expect(builderName, 'prioritizedBuilder');
313 | },
314 | );
315 | });
316 |
317 | group('CalendarBuilders Locale test:', () {
318 | testWidgets('en locale with default dayTextFormatter', (tester) async {
319 | const locale = 'en';
320 | initializeDateFormatting(locale);
321 |
322 | final cellDay = DateTime.utc(2021, 7, 15);
323 | await tester.pumpWidget(
324 | setupTestWidget(
325 | cellDay,
326 | locale: locale,
327 | ),
328 | );
329 |
330 | final dayFinder = find.text('${cellDay.day}');
331 | expect(dayFinder, findsOneWidget);
332 | });
333 |
334 | testWidgets('en locale with custom dayTextFormatter', (tester) async {
335 | const locale = 'en';
336 | initializeDateFormatting(locale);
337 |
338 | final cellDay = DateTime.utc(2021, 7, 15);
339 | await tester.pumpWidget(
340 | setupTestWidget(
341 | cellDay,
342 | locale: locale,
343 | calendarStyle: CalendarStyle(
344 | dayTextFormatter: (date, locale) =>
345 | DateFormat.d(locale).format(date),
346 | ),
347 | ),
348 | );
349 |
350 | final dayFinder = find.text(DateFormat.d(locale).format(cellDay));
351 | expect(dayFinder, findsOneWidget);
352 | });
353 |
354 | testWidgets('ar locale with default dayTextFormatter', (tester) async {
355 | const locale = 'ar';
356 | initializeDateFormatting(locale);
357 |
358 | final cellDay = DateTime.utc(2021, 7, 15);
359 | await tester.pumpWidget(
360 | setupTestWidget(
361 | cellDay,
362 | locale: locale,
363 | ),
364 | );
365 |
366 | final dayFinder = find.text('${cellDay.day}');
367 | expect(dayFinder, findsOneWidget);
368 | });
369 |
370 | testWidgets('ar locale with custom dayTextFormatter', (tester) async {
371 | const locale = 'ar';
372 | initializeDateFormatting(locale);
373 |
374 | final cellDay = DateTime.utc(2021, 7, 15);
375 | await tester.pumpWidget(
376 | setupTestWidget(
377 | cellDay,
378 | locale: locale,
379 | calendarStyle: CalendarStyle(
380 | dayTextFormatter: (date, locale) =>
381 | DateFormat.d(locale).format(date),
382 | ),
383 | ),
384 | );
385 |
386 | final dayFinder = find.text(DateFormat.d(locale).format(cellDay));
387 | expect(dayFinder, findsOneWidget);
388 | });
389 | });
390 | }
391 |
--------------------------------------------------------------------------------
/test/common.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/foundation.dart';
5 | import 'package:table_calendar/src/shared/utils.dart';
6 |
7 | ValueKey dateToKey(DateTime date, {String prefix = ''}) {
8 | return ValueKey('$prefix${date.year}-${date.month}-${date.day}');
9 | }
10 |
11 | const calendarFormatMap = {
12 | CalendarFormat.month: 'Month',
13 | CalendarFormat.twoWeeks: 'Two weeks',
14 | CalendarFormat.week: 'week',
15 | };
16 |
--------------------------------------------------------------------------------
/test/custom_icon_button_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_test/flutter_test.dart';
6 | import 'package:table_calendar/src/widgets/custom_icon_button.dart';
7 |
8 | Widget setupTestWidget(Widget child) {
9 | return Directionality(
10 | textDirection: TextDirection.ltr,
11 | child: Material(child: child),
12 | );
13 | }
14 |
15 | void main() {
16 | testWidgets(
17 | 'onTap gets called when CustomIconButton is tapped',
18 | (tester) async {
19 | bool buttonTapped = false;
20 |
21 | await tester.pumpWidget(
22 | setupTestWidget(
23 | CustomIconButton(
24 | icon: const Icon(Icons.chevron_left),
25 | onTap: () {
26 | buttonTapped = true;
27 | },
28 | ),
29 | ),
30 | );
31 |
32 | final button = find.byType(CustomIconButton);
33 | expect(button, findsOneWidget);
34 | expect(buttonTapped, false);
35 |
36 | await tester.tap(button);
37 | await tester.pumpAndSettle();
38 | expect(buttonTapped, true);
39 | },
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/test/format_button_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_test/flutter_test.dart';
6 | import 'package:table_calendar/src/widgets/format_button.dart';
7 | import 'package:table_calendar/table_calendar.dart';
8 |
9 | import 'common.dart';
10 |
11 | Widget setupTestWidget(Widget child) {
12 | return Directionality(
13 | textDirection: TextDirection.ltr,
14 | child: Material(child: child),
15 | );
16 | }
17 |
18 | void main() {
19 | group('onTap callback tests:', () {
20 | testWidgets(
21 | 'Initial format month returns twoWeeks when tapped',
22 | (tester) async {
23 | const headerStyle = HeaderStyle();
24 | CalendarFormat? calendarFormat;
25 |
26 | await tester.pumpWidget(
27 | setupTestWidget(
28 | FormatButton(
29 | availableCalendarFormats: calendarFormatMap,
30 | calendarFormat: CalendarFormat.month,
31 | decoration: headerStyle.formatButtonDecoration,
32 | padding: headerStyle.formatButtonPadding,
33 | textStyle: headerStyle.formatButtonTextStyle,
34 | showsNextFormat: headerStyle.formatButtonShowsNext,
35 | onTap: (format) {
36 | calendarFormat = format;
37 | },
38 | ),
39 | ),
40 | );
41 |
42 | expect(find.byType(FormatButton), findsOneWidget);
43 | expect(calendarFormat, isNull);
44 |
45 | await tester.tap(find.byType(FormatButton));
46 | await tester.pumpAndSettle();
47 | expect(calendarFormat, CalendarFormat.twoWeeks);
48 | },
49 | );
50 |
51 | testWidgets(
52 | 'Initial format twoWeeks returns week when tapped',
53 | (tester) async {
54 | const headerStyle = HeaderStyle();
55 | CalendarFormat? calendarFormat;
56 |
57 | await tester.pumpWidget(
58 | setupTestWidget(
59 | FormatButton(
60 | availableCalendarFormats: calendarFormatMap,
61 | calendarFormat: CalendarFormat.twoWeeks,
62 | decoration: headerStyle.formatButtonDecoration,
63 | padding: headerStyle.formatButtonPadding,
64 | textStyle: headerStyle.formatButtonTextStyle,
65 | showsNextFormat: headerStyle.formatButtonShowsNext,
66 | onTap: (format) {
67 | calendarFormat = format;
68 | },
69 | ),
70 | ),
71 | );
72 |
73 | expect(find.byType(FormatButton), findsOneWidget);
74 | expect(calendarFormat, isNull);
75 |
76 | await tester.tap(find.byType(FormatButton));
77 | await tester.pumpAndSettle();
78 | expect(calendarFormat, CalendarFormat.week);
79 | },
80 | );
81 |
82 | testWidgets(
83 | 'Initial format week return month when tapped',
84 | (tester) async {
85 | const headerStyle = HeaderStyle();
86 | CalendarFormat? calendarFormat;
87 |
88 | await tester.pumpWidget(
89 | setupTestWidget(
90 | FormatButton(
91 | availableCalendarFormats: calendarFormatMap,
92 | calendarFormat: CalendarFormat.week,
93 | decoration: headerStyle.formatButtonDecoration,
94 | padding: headerStyle.formatButtonPadding,
95 | textStyle: headerStyle.formatButtonTextStyle,
96 | showsNextFormat: headerStyle.formatButtonShowsNext,
97 | onTap: (format) {
98 | calendarFormat = format;
99 | },
100 | ),
101 | ),
102 | );
103 |
104 | expect(find.byType(FormatButton), findsOneWidget);
105 | expect(calendarFormat, isNull);
106 |
107 | await tester.tap(find.byType(FormatButton));
108 | await tester.pumpAndSettle();
109 | expect(calendarFormat, CalendarFormat.month);
110 | },
111 | );
112 | });
113 |
114 | group('showsNextFormat tests:', () {
115 | testWidgets(
116 | 'true - display next calendar format',
117 | (tester) async {
118 | const headerStyle = HeaderStyle();
119 |
120 | const currentFormatIndex = 0;
121 | final currentFormat =
122 | calendarFormatMap.keys.elementAt(currentFormatIndex);
123 | final currentFormatText =
124 | calendarFormatMap.values.elementAt(currentFormatIndex);
125 |
126 | const nextFormatIndex = 1;
127 | final nextFormatText =
128 | calendarFormatMap.values.elementAt(nextFormatIndex);
129 |
130 | await tester.pumpWidget(
131 | setupTestWidget(
132 | FormatButton(
133 | availableCalendarFormats: calendarFormatMap,
134 | calendarFormat: currentFormat,
135 | decoration: headerStyle.formatButtonDecoration,
136 | padding: headerStyle.formatButtonPadding,
137 | textStyle: headerStyle.formatButtonTextStyle,
138 | showsNextFormat: headerStyle.formatButtonShowsNext,
139 | onTap: (format) {},
140 | ),
141 | ),
142 | );
143 |
144 | expect(find.byType(FormatButton), findsOneWidget);
145 | expect(currentFormatText, isNotNull);
146 | expect(find.text(currentFormatText), findsNothing);
147 | expect(nextFormatText, isNotNull);
148 | expect(find.text(nextFormatText), findsOneWidget);
149 | },
150 | );
151 |
152 | testWidgets(
153 | 'false - display current calendar format',
154 | (tester) async {
155 | const headerStyle = HeaderStyle(formatButtonShowsNext: false);
156 |
157 | const currentFormatIndex = 0;
158 | final currentFormat =
159 | calendarFormatMap.keys.elementAt(currentFormatIndex);
160 | final currentFormatText =
161 | calendarFormatMap.values.elementAt(currentFormatIndex);
162 |
163 | await tester.pumpWidget(
164 | setupTestWidget(
165 | FormatButton(
166 | availableCalendarFormats: calendarFormatMap,
167 | calendarFormat: currentFormat,
168 | decoration: headerStyle.formatButtonDecoration,
169 | padding: headerStyle.formatButtonPadding,
170 | textStyle: headerStyle.formatButtonTextStyle,
171 | showsNextFormat: headerStyle.formatButtonShowsNext,
172 | onTap: (format) {},
173 | ),
174 | ),
175 | );
176 |
177 | expect(find.byType(FormatButton), findsOneWidget);
178 | expect(currentFormatText, isNotNull);
179 | expect(find.text(currentFormatText), findsOneWidget);
180 | },
181 | );
182 | });
183 | }
184 |
--------------------------------------------------------------------------------
/test/utils_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Aleksander Woźniak
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 | import 'package:flutter_test/flutter_test.dart';
5 | import 'package:table_calendar/src/shared/utils.dart';
6 |
7 | void main() {
8 | group('isSameDay() tests:', () {
9 | test('Same day, different time', () {
10 | final dateA = DateTime(2020, 5, 10, 4, 32, 16);
11 | final dateB = DateTime(2020, 5, 10, 8, 21, 44);
12 |
13 | expect(isSameDay(dateA, dateB), true);
14 | });
15 |
16 | test('Different day, same time', () {
17 | final dateA = DateTime(2020, 5, 10, 4, 32, 16);
18 | final dateB = DateTime(2020, 5, 11, 4, 32, 16);
19 |
20 | expect(isSameDay(dateA, dateB), false);
21 | });
22 |
23 | test('UTC and local time zone', () {
24 | final dateA = DateTime.utc(2020, 5, 10);
25 | final dateB = DateTime(2020, 5, 10);
26 |
27 | expect(isSameDay(dateA, dateB), true);
28 | });
29 | });
30 |
31 | group('normalizeDate() tests:', () {
32 | test('Local time zone gets converted to UTC', () {
33 | final dateA = DateTime(2020, 5, 10, 4, 32, 16);
34 | final dateB = normalizeDate(dateA);
35 |
36 | expect(dateB.isUtc, true);
37 | });
38 |
39 | test('Date is unchanged', () {
40 | final dateA = DateTime(2020, 5, 10, 4, 32, 16);
41 | final dateB = normalizeDate(dateA);
42 |
43 | expect(dateB.year, 2020);
44 | expect(dateB.month, 5);
45 | expect(dateB.day, 10);
46 | });
47 |
48 | test('Time gets trimmed', () {
49 | final dateA = DateTime(2020, 5, 10, 4, 32, 16);
50 | final dateB = normalizeDate(dateA);
51 |
52 | expect(dateB.hour, 0);
53 | expect(dateB.minute, 0);
54 | expect(dateB.second, 0);
55 | expect(dateB.millisecond, 0);
56 | expect(dateB.microsecond, 0);
57 | });
58 | });
59 |
60 | group('getWeekdayNumber() tests:', () {
61 | test('Monday returns number 1', () {
62 | expect(getWeekdayNumber(StartingDayOfWeek.monday), 1);
63 | });
64 |
65 | test('Sunday returns number 7', () {
66 | expect(getWeekdayNumber(StartingDayOfWeek.sunday), 7);
67 | });
68 | });
69 | }
70 |
--------------------------------------------------------------------------------