diff options
Diffstat (limited to 'packages')
30 files changed, 3129 insertions, 0 deletions
diff --git a/packages/flutter_calendar_carousel/.coveralls.yml b/packages/flutter_calendar_carousel/.coveralls.yml new file mode 100644 index 0000000..121a96a --- /dev/null +++ b/packages/flutter_calendar_carousel/.coveralls.yml @@ -0,0 +1 @@ +repo_token: ZZjgwJha5lf2FFsNyvzmbNsswFz2fZuKR diff --git a/packages/flutter_calendar_carousel/.github/workflows/ci.yml b/packages/flutter_calendar_carousel/.github/workflows/ci.yml new file mode 100644 index 0000000..bc4461d --- /dev/null +++ b/packages/flutter_calendar_carousel/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: Flutter CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4 + - uses: actions/setup-java@v4 + with: + java-version: "14.x" + + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + flutter-version: "3.x" + + - run: flutter pub get + + - run: flutter format --set-exit-if-changed . + + # - run: flutter analyze . + + - run: flutter test --coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: coverage/lcov.info + + # - run: flutter build apk + + # Upload generated apk to the artifacts. + # - uses: actions/upload-artifact@v1 + # with: + # name: release-apk + # path: build/app/outputs/apk/release/app-release.apk diff --git a/packages/flutter_calendar_carousel/.github/workflows/stale.yml b/packages/flutter_calendar_carousel/.github/workflows/stale.yml new file mode 100644 index 0000000..f2b97a0 --- /dev/null +++ b/packages/flutter_calendar_carousel/.github/workflows/stale.yml @@ -0,0 +1,21 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 90 + days-before-close: 7 + stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Leave a comment or this will be closed in 7 days.' + stale-pr-message: 'This PR is stale because it has been open 90 days with no activity. Leave a comment or this will be closed in 7 days' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' diff --git a/packages/flutter_calendar_carousel/.gitignore b/packages/flutter_calendar_carousel/.gitignore new file mode 100644 index 0000000..f7ea22e --- /dev/null +++ b/packages/flutter_calendar_carousel/.gitignore @@ -0,0 +1,16 @@ +.DS_Store +.dart_tool/ +.vscode/ + +.packages +.pub/ +coverage + +build/ +ios/.generated/ +ios/Flutter/Generated.xcconfig +ios/Runner/GeneratedPluginRegistrant.* + +.idea/ +flutter_calendar_carousel.iml +flutter_export_environment.sh diff --git a/packages/flutter_calendar_carousel/.travis.yml b/packages/flutter_calendar_carousel/.travis.yml new file mode 100644 index 0000000..4a8e0f5 --- /dev/null +++ b/packages/flutter_calendar_carousel/.travis.yml @@ -0,0 +1,90 @@ +matrix: + # This causes the build to complete immediately upon first failure or once + # required jobs are green. + fast_finish: true + + # Building APK/IPA takes a long time; do not wait for them to finish. + # allow_failures: + # - env: JOB=APK + # - env: JOB=IPA + + include: + # Runs unit tests without emulator. + - env: JOB=PR + os: linux + language: dart + sudo: false + addons: + apt: + # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 + sources: + - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version + packages: + - libstdc++6 + install: + - echo 'Avoid default Travis CI install step' + before_script: + - git clone https://github.com/flutter/flutter.git -b stable + - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH + - flutter doctor + - gem install coveralls-lcov + script: + - ./flutter/bin/flutter test --coverage + after_success: + - coveralls-lcov coverage/lcov.info + +# # Builds an APK. +# - env: JOB=APK +# os: linux +# language: android +# licenses: +# - 'android-sdk-preview-license-.+' +# - 'android-sdk-license-.+' +# - 'google-gdk-license-.+' +# android: +# components: +# - tools +# - platform-tools +# - build-tools-25.0.3 +# - android-25 +# - sys-img-armeabi-v7a-google_apis-25 +# - extra-android-m2repository +# - extra-google-m2repository +# - extra-google-android-support +# jdk: oraclejdk8 +# sudo: false +# addons: +# apt: +# # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 +# sources: +# - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version +# packages: +# - libstdc++6 +# - fonts-droid +# before_script: +# - wget http://services.gradle.org/distributions/gradle-3.5-bin.zip +# - unzip -qq gradle-3.5-bin.zip +# - export GRADLE_HOME=$PWD/gradle-3.5 +# - export PATH=$GRADLE_HOME/bin:$PATH +# - git clone https://github.com/flutter/flutter.git -b beta +# script: +# - ./flutter/bin/flutter -v build apk +# +# # Builds an IPA. +# - env: JOB=IPA +# os: osx +# language: generic +# osx_image: xcode8.3 +# before_script: +# - pip install six +# - brew update +# - brew install --HEAD libimobiledevice +# - brew install ideviceinstaller +# - brew install ios-deploy +# - git clone https://github.com/flutter/flutter.git -b beta +# script: +# - ./flutter/bin/flutter -v build ios --no-codesign + +cache: + directories: + - $HOME/.pub-cache diff --git a/packages/flutter_calendar_carousel/CHANGELOG.md b/packages/flutter_calendar_carousel/CHANGELOG.md new file mode 100644 index 0000000..aa8ec71 --- /dev/null +++ b/packages/flutter_calendar_carousel/CHANGELOG.md @@ -0,0 +1,306 @@ +## [2.4.3] + +Upgrade intl to `^0.18.1` + +## [2.4.2] + +Support Flutter version 3+ + +## [2.4.1] + +Update iOS podspec and `info.plist` + +## [2.1.0] + +Update build config on flutter V2 embedding (#293) + +## [2.0.3] + +Multiple days selection using `addRange` method [#285](https://github.com/dooboolab/flutter_calendar_carousel/pull/285) + +## [2.0.2] + +Multiple days selection [#282](https://github.com/dooboolab/flutter_calendar_carousel/pull/284) + +## [2.0.1] + +Null safety improvements [#272](https://github.com/dooboolab/flutter_calendar_carousel/pull/272) + +## [2.0.1] + +Added disableDayPressed option [#267](https://github.com/dooboolab/flutter_calendar_carousel/pull/267) + +## [2.0.0] + +Support null-safety [#260](https://github.com/dooboolab/flutter_calendar_carousel/pull/260) + +## [1.5.3] + +- Add `id` to event model [#257](https://github.com/dooboolab/flutter_calendar_carousel/pull/257) + +## [1.5.2] + +- Bump up `intl` dependency [#254](https://github.com/dooboolab/flutter_calendar_carousel/pull/254) + +## [1.5.1] + +- Bugfix when switching month - "The method 'call' was called on null." [#243](https://github.com/dooboolab/flutter_calendar_carousel/pull/243) + +## [1.5.0] + +- Add key to widget constructor [#234](https://github.com/dooboolab/flutter_calendar_carousel/pull/234/files) +- Enhance initilizing page numbers [#231](https://github.com/dooboolab/flutter_calendar_carousel/pull/231) + +## [1.4.12] + +- Handle issue [#207](https://github.com/dooboolab/flutter_calendar_carousel/issues/207), [#209](https://github.com/dooboolab/flutter_calendar_carousel/issues/209) + +## [1.4.11] + +- Add first day of week offset to week builder [#204](https://github.com/dooboolab/flutter_calendar_carousel/pull/204) + +## [1.4.10] + +- Fix Calendar displays incorrectly when scrolling horizontally [#193](https://github.com/dooboolab/flutter_calendar_carousel/pull/193) + +## [1.4.9] + +- Target date for custom header + +## [1.4.8] + +- Add ability to set `targetDate` on header [#183](https://github.com/dooboolab/flutter_calendar_carousel/pull/183). + +## [1.4.7] + +- Fix current day showing incorrectly when using `showOnlyCurrentMonthDate` [#181](https://github.com/dooboolab/flutter_calendar_carousel/pull/182). + +## [1.4.6] + +- Set default `minSelectedDate` and `maxSelectedDate` [#179](https://github.com/dooboolab/flutter_calendar_carousel/pull/179). + +## [1.4.4] + +- Expose `pageScrollPhysics` for pageView. + +## [1.4.2] + +- Add option for setting scrollDirection [#166](https://github.com/dooboolab/flutter_calendar_carousel/pull/166) +- Resolve [#123](https://github.com/dooboolab/flutter_calendar_carousel/issues/123) in [#165](https://github.com/dooboolab/flutter_calendar_carousel/pull/165). + +## [1.4.1] + +- Resolve [#164](https://github.com/dooboolab/flutter_calendar_carousel/issues/164). + +## [1.4.0] + +- Resolve [#154](https://github.com/dooboolab/flutter_calendar_carousel/issues/154). + +## [1.3.29] + +- Resolve [#157](https://github.com/dooboolab/flutter_calendar_carousel/issues/157). + +## [1.3.28] + +- Allow the use of generic type with Interface [#149](https://github.com/dooboolab/flutter_calendar_carousel/pull/149) +- Added doc to custom weekday builder. Weekday number is now supplied to the builder [#150](https://github.com/dooboolab/flutter_calendar_carousel/pull/150) + +## [1.3.27] + +- customDayBuilder fix. +- Remove date_utils dep. + +## [1.3.26] + +- Support custom day container feature [#145](https://github.com/dooboolab/flutter_calendar_carousel/pull/145). + +## [1.3.23] + +- Support intl >= 0.15.7 < 0.17.0 to inclease `pub` health +- Removed deprecated methods ~~`markedDates`~~, ~~`markedDateColor`~~ +- Fixes [#101](https://github.com/dooboolab/flutter_calendar_carousel/issues/101) +- Fixes [#104](https://github.com/dooboolab/flutter_calendar_carousel/issues/104) +- Fixes [#112](https://github.com/dooboolab/flutter_calendar_carousel/issues/112) +- Fixes [#119](https://github.com/dooboolab/flutter_calendar_carousel/issues/119) +- Support long pressed as a feature request[#103](https://github.com/dooboolab/flutter_calendar_carousel/issues/103) +- Support semantic label as a feature request [#139](https://github.com/dooboolab/flutter_calendar_carousel/issues/139) +- Expose `dayCrossAxisAlignment` and `dayMainAxisAlignment` to resolve [#122](https://github.com/dooboolab/flutter_calendar_carousel/issues/122) +- Expose `showIconBehindDayText` to resolve [#131](https://github.com/dooboolab/flutter_calendar_carousel/issues/131) +- Fixes [#94](https://github.com/dooboolab/flutter_calendar_carousel/issues/94) + +## [1.3.20] + +- Support intl >= 0.15.7 + +## [1.3.19] + +- Improved customizability for weekday containers [#141](https://github.com/dooboolab/flutter_calendar_carousel/pull/141) + +## [1.3.18] + +- Fix vertical scroll behavior for weekFormat calendar view. +- Reformat code with dartfmt + +## [1.3.17] + +- Added feature to only show dates from today adding `showOnlyCurrentMonthDate` parameter. + +## [1.3.16] + +- Added feature for change first day of the week. + +## [1.3.15+] + +- Ability to disable horizontal scroll to change month with `isScrollable` param. + - Resolve [#74](https://github.com/dooboolab/flutter_calendar_carousel/issues/74) +- Show events in `week` calendar. + - Resolve [#66](https://github.com/dooboolab/flutter_calendar_carousel/issues/66) +- Update breaking docs in pub. +- Expose event list to user. +- Remove print. +- Pass first date of week to onCalendarChanged in week view [#88](https://github.com/dooboolab/flutter_calendar_carousel/pull/88) +- Support for passing in custom widgets for next and previous month arrow icons [#95](https://github.com/dooboolab/flutter_calendar_carousel/pull/95) + +## [1.3.14] + +- Code refactoring [#77](https://github.com/dooboolab/flutter_calendar_carousel/pull/77) + - Seperate weekday widget + +## [1.3.13] + +- Code refactoring [#73](https://github.com/dooboolab/flutter_calendar_carousel/pull/73) + - Seperate header. + - Add first basic test code. + - Add composable header widget + +## [1.3.12] + +- Setting dot icon per event [#71](https://github.com/dooboolab/flutter_calendar_carousel/pull/71) + +## [1.3.11] + +- Fixed `selectledDayTextStyle` property not being respected [#65](https://github.com/dooboolab/flutter_calendar_carousel/pull/65). + +## [1.3.10] + +- Add property for static six week format to keep calendar height consistent between months [#62](https://github.com/dooboolab/flutter_calendar_carousel/pull/62). + +## [1.3.9] + +- Changed priority for `today` higher than `prevMonth` and `nextMonth` and `thisMonth`. + +## [1.3.7] + +- Ability to choose the weekday format on the constructor [#47](https://github.com/dooboolab/flutter_calendar_carousel/pull/47). + +## [1.3.6] + +- custom event type added [#49](https://github.com/dooboolab/flutter_calendar_carousel/pull/49). + +## [1.3.5] + +- headerTitleTouchable and onHeaderTitlePressed props added [#44](https://github.com/dooboolab/flutter_calendar_carousel/pull/44). + +## [1.3.4] + +- Bug fix. PrevDaysTextStyle and PrevDaysTextStyle overwrite weekendTextStyle [#41](https://github.com/dooboolab/flutter_calendar_carousel/issues/41). + +## [1.3.3] + +- Fixed FlatButton fill and border color. [#37](https://github.com/dooboolab/flutter_calendar_carousel/pull/37) +- EventList bug fixing. [#37](https://github.com/dooboolab/flutter_calendar_carousel/pull/36) + +## [1.3.2] + +- Mapping events for better performance. [#34](https://github.com/dooboolab/flutter_calendar_carousel/pull/34). + +## [1.3.1] + +- weekdays bug fix. + +## [1.3.0] + +- Better localization support for `weekDays`. Setting manually weekdays isn't required now. Related [#23](https://github.com/dooboolab/flutter_calendar_carousel/pull/23). +- Add custom icons in event [#28](https://github.com/dooboolab/flutter_calendar_carousel/pull/28). + +## [1.2.3] + +- Add custom physics parameter. Feature in [#21](https://github.com/dooboolab/flutter_calendar_carousel/pull/21). + +## [1.2.2] + +- headerTextStyle fix[#17](https://github.com/dooboolab/flutter_calendar_carousel/issues/17). +- Can show or hide header button with `showHeaderButton` attribute. + +## [1.2.1] + +- Week-format shows current week [#15](https://github.com/dooboolab/flutter_calendar_carousel/issues/15). + +## [1.2.0] + +- Support carousel week calendar. + +## [1.1.11] + +- Ability to customize weekend days [#13](https://github.com/dooboolab/flutter_calendar_carousel/issues/13). + +## [1.1.10] + +- Support weekFormat but without carousel. + +## [1.1.9] + +- Updated readme. + +## [1.1.8] + +- Render multiple marked dates. +- `markedDates` is deprecated. Use `markedDatesMap` instead. + +## [1.1.3] + +- Mark dates with non-zero times. + +## [1.1.2] + +- Implemented a way to change the header text style. + +## [1.1.1] + +- Expose new variables. + - headerMargin, childAspectRatio, weekDayMargin + +## [1.1.0] + +- Give proper text color in weekend when it is today. +- Compare month and year for marking today's date. Resolve #3. + +## [1.0.3] + +- Fixed pub broken image. + +## [1.0.2] + +- Use `Position` widget to mark the dates. + +## [1.0.1] + +- Show markedDates. + +## [0.2.0] + +- Customizable headerWidget. +- Setting weekdays visibility +- Customizable weekend color. + +## [0.1.3] + +- Rename the top-level "docs" directory to "doc". + +## [0.1.1] + +- Added readme. + +## [0.1.0] + +- First release diff --git a/packages/flutter_calendar_carousel/LICENSE b/packages/flutter_calendar_carousel/LICENSE new file mode 100644 index 0000000..74022ad --- /dev/null +++ b/packages/flutter_calendar_carousel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 dooboolab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/flutter_calendar_carousel/README.md b/packages/flutter_calendar_carousel/README.md new file mode 100644 index 0000000..daff642 --- /dev/null +++ b/packages/flutter_calendar_carousel/README.md @@ -0,0 +1,188 @@ +# flutter_calendar_carousel + +[![Pub Version](https://img.shields.io/pub/v/flutter_calendar_carousel.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_calendar_carousel) +[![Flutter CI](https://github.com/dooboolab/flutter_calendar_carousel/actions/workflows/ci.yml/badge.svg)](https://github.com/dooboolab/flutter_calendar_carousel/actions/workflows/ci.yml) +[![Coverage Status](https://codecov.io/gh/dooboolab/flutter_calendar_carousel/branch/master/graph/badge.svg?token=KTrSs3fGsS)](https://codecov.io/gh/dooboolab/flutter_calendar_carousel) +![License](https://img.shields.io/badge/license-MIT-blue.svg) + +Calendar widget for flutter that is swipeable horizontally. This widget can help you build your own calendar widget highly customizable. Now you can even add your icon for each event. + +## Notice +This widget is compatible with flutter V3 from version `2.4.+`. + +## New Feature + +[2.0.3] + +- Multiple days selection using `addRange` method [#285](https://github.com/dooboolab/flutter_calendar_carousel/pull/285) + +- Check out great feature `customDayBuilder` work done by [maxgmer](https://github.com/maxgmer) :tada:. + +#### Rectangular style + +![image](https://raw.githubusercontent.com/dooboolab/flutter_calendar_carousel/master/doc/calendar1.gif) + +#### Circular style + +![image](https://raw.githubusercontent.com/dooboolab/flutter_calendar_carousel/master/doc/calendar2.gif) + +#### No border + +![image](https://raw.githubusercontent.com/dooboolab/flutter_calendar_carousel/master/doc/calendar3.gif) + +#### Marked Dates + +![image](https://raw.githubusercontent.com/dooboolab/flutter_calendar_carousel/master/doc/calendar4.gif) + +#### Custom Icon Events + +![image](https://raw.githubusercontent.com/icemanbsi/flutter_calendar_carousel/master/doc/calendar5.gif) + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](https://flutter.io/). + +## Props + +| props | types | defaultValues | +| :----------------------------- | :------------------------: | :---------------------------------------------------------------------------------------------------------: | +| viewPortFraction | `double` | 1.0 | +| prevDaysTextStyle | `TextStyle` | | +| daysTextStyle | `TextStyle` | | +| nextDaysTextStyle | `TextStyle` | | +| prevMonthDayBorderColor | `Color` | Colors.transparent | +| thisMonthDayBorderColor | `Color` | Colors.transparent | +| nextMonthDayBorderColor | `Color` | Colors.transparent | +| dayPadding | `double` | 2.0 | +| height | `double` | double.infinity | +| width | `double` | double.infinity | +| todayTextStyle | `TextStyle` | `fontSize: 14.0, color: Colors.white` | +| dayButtonColor | `Color` | Colors.red | +| todayBorderColor | `Color` | Colors.red | +| todayButtonColor | `Colors` | Colors.red | +| selectedDateTime | `DateTime` | | +| selectedDayTextStyle | `TextStyle` | `fontSize: 14.0, color: Colors.white` | +| selectedDayBorderColor | `Color` | Colors.green | +| selectedDayButtonColor | `Color` | Colors.green | +| daysHaveCircularBorder | `bool` | | +| onDayPressed | `Func` | | +| weekdayTextStyle | `TextStyle` | `fontSize: 14.0, color: Colors.deepOrange` | +| iconColor | `Color` | Colors.blueAccent | +| headerTextStyle | `TextStyle` | `fontSize: 20.0, color: Colors.blue` | +| headerText | `Text` | `Text('${DateFormat.yMMM().format(this._dates[1])}'`) | +| weekendTextStyle | `TextStyle` | `fontSize: 14.0, color: Colors.pinkAccent` | +| markedDatesMap | `Events` | `null` | +| markedDateWidget | `Widget` | `Positioned(child: Container(color: Colors.blueAccent, height: 4.0, width: 4.0), bottom: 4.0, left: 18.0);` | +| markedDateShowIcon | `bool` | false | +| markedDateIconBorderColor | `Color` | | +| markedDateIconMaxShown | `int` | 2 | +| markedDateIconMargin | `double` | 5.0 | +| markedDateIconBuilder | `MarkedDateIconBuilder<T>` | | +| markedDateIconOffset | `double` | 5.0 | +| markedDateCustomShapeBorder | `ShapeBorder` | null | +| markedDateCustomTextStyle | `TextStyle` | null | +| markedDateMoreCustomDecoration | `Decoration` | | +| markedDateMoreCustomTextStyle | `TextStyle` | | +| headerMargin | `EdgetInsets` | `const EdgeInsets.symmetric(vertical: 16.0)` | +| headerTitleTouchable | `bool` | `false` | +| onHeaderTitlePressed | `Function` | `() => _selectDateFromPicker()` | +| showHeader | `bool` | | +| showHeaderButton | `bool` | | +| childAspectRatio | `double` | `1.0` | +| weekDayMargin | `EdgeInsets` | `const EdgeInsets.only(bottom: 4.0)` | +| weekFormat | `bool` | `false` | +| locale | `String` | `en` | +| firstDayOfWeek | `int` | `null` | +| onCalendarChanged | `Function(DateTime)` | | +| minSelectedDate | `DateTime` | | +| maxSelectedDate | `DateTime` | | +| inactiveDaysTextStyle | `TextStyle` | | +| inactiveWeekendTextStyle | `TextStyle` | | +| weekDayFormat | `WeekdayFormat` | `short` | +| staticSixWeekFormat | `bool` | `false` | +| showOnlyCurrentMonthDate | `bool` | `false` | +| dayCrossAxisAlignment | `CrossAxisAlignment` | `CrossAxisAlignment.center` | +| dayMainAxisAlignment | `MainAxisAlignment` | `CrossAlignment.center` | +| showIconBehindDayText | `bool` | `false` | +| pageScrollPhysics | `ScrollPhysics` | `ScrollPhysics` | + +With `CalendarCarousel<YourEventClass>` and `EventList<YourEventClass>` you can specifiy a custom Event class. + +## Install + +Add `flutter_calendar_carousel` as a dependency in pubspec.yaml +For help on adding as a dependency, view the [documentation](https://flutter.io/using-packages/). + +## Usage + +```dart +import 'package:flutter_calendar_carousel/flutter_calendar_carousel.dart' show CalendarCarousel; +Widget widget() { + return Container( + margin: EdgeInsets.symmetric(horizontal: 16.0), + child: CalendarCarousel<Event>( + onDayPressed: (DateTime date, List<Event> events) { + this.setState(() => _currentDate = date); + }, + weekendTextStyle: TextStyle( + color: Colors.red, + ), + thisMonthDayBorderColor: Colors.grey, +// weekDays: null, /// for pass null when you do not want to render weekDays +// headerText: Container( /// Example for rendering custom header +// child: Text('Custom Header'), +// ), + customDayBuilder: ( /// you can provide your own build function to make custom day containers + bool isSelectable, + int index, + bool isSelectedDay, + bool isToday, + bool isPrevMonthDay, + TextStyle textStyle, + bool isNextMonthDay, + bool isThisMonthDay, + DateTime day, + ) { + /// If you return null, [CalendarCarousel] will build container for current [day] with default function. + /// This way you can build custom containers for specific days only, leaving rest as default. + + // Example: every 15th of month, we have a flight, we can place an icon in the container like that: + if (day.day == 15) { + return Center( + child: Icon(Icons.local_airport), + ); + } else { + return null; + } + }, + weekFormat: false, + markedDatesMap: _markedDateMap, + height: 420.0, + selectedDateTime: _currentDate, + daysHaveCircularBorder: false, /// null for not rendering any border, true for circular border, false for rectangular border + ), + ); +} +``` + +### TODO + +- [x] Render weekdays. +- [x] Customizable headerWidget. +- [x] Set weekdays visibility. +- [x] Customizable textStyles for days in weekend. +- [x] Marked Dates. +- [x] Multiple Marked Dates. +- [x] Customizable weekend days. +- [x] Week Calendar. +- [x] Carousel Week Calendar. +- [ ] Multiple days selections. +- [x] Widget test. + +## Help Maintenance + +I've been maintaining quite many repos these days and burning out slowly. If you could help me cheer up, buying me a cup of coffee will make my life really happy and get much energy out of it. +<br/> +<a href="https://www.buymeacoffee.com/dooboolab" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/purple_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a> +[![Paypal](https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png)](https://paypal.me/dooboolab) diff --git a/packages/flutter_calendar_carousel/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/packages/flutter_calendar_carousel/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 0000000..d007606 --- /dev/null +++ b/packages/flutter_calendar_carousel/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,23 @@ +package io.flutter.plugins; + +import io.flutter.plugin.common.PluginRegistry; + +/** + * Generated file. Do not edit. + */ +public final class GeneratedPluginRegistrant { + public static void registerWith(PluginRegistry registry) { + if (alreadyRegisteredWith(registry)) { + return; + } + } + + private static boolean alreadyRegisteredWith(PluginRegistry registry) { + final String key = GeneratedPluginRegistrant.class.getCanonicalName(); + if (registry.hasPlugin(key)) { + return true; + } + registry.registrarFor(key); + return false; + } +} diff --git a/packages/flutter_calendar_carousel/android/local.properties b/packages/flutter_calendar_carousel/android/local.properties new file mode 100644 index 0000000..81dcc8b --- /dev/null +++ b/packages/flutter_calendar_carousel/android/local.properties @@ -0,0 +1,3 @@ +sdk.dir=/Users/hyo/Library/Android/sdk +flutter.sdk=/Users/hyo/Github/others/flutter +flutter.versionName=0.0.1
\ No newline at end of file diff --git a/packages/flutter_calendar_carousel/doc/calendar1.gif b/packages/flutter_calendar_carousel/doc/calendar1.gif Binary files differnew file mode 100644 index 0000000..3974a5f --- /dev/null +++ b/packages/flutter_calendar_carousel/doc/calendar1.gif diff --git a/packages/flutter_calendar_carousel/doc/calendar2.gif b/packages/flutter_calendar_carousel/doc/calendar2.gif Binary files differnew file mode 100644 index 0000000..258c372 --- /dev/null +++ b/packages/flutter_calendar_carousel/doc/calendar2.gif diff --git a/packages/flutter_calendar_carousel/doc/calendar3.gif b/packages/flutter_calendar_carousel/doc/calendar3.gif Binary files differnew file mode 100644 index 0000000..d5bda07 --- /dev/null +++ b/packages/flutter_calendar_carousel/doc/calendar3.gif diff --git a/packages/flutter_calendar_carousel/doc/calendar4.gif b/packages/flutter_calendar_carousel/doc/calendar4.gif Binary files differnew file mode 100644 index 0000000..5006372 --- /dev/null +++ b/packages/flutter_calendar_carousel/doc/calendar4.gif diff --git a/packages/flutter_calendar_carousel/doc/calendar5.gif b/packages/flutter_calendar_carousel/doc/calendar5.gif Binary files differnew file mode 100644 index 0000000..bd4cdb1 --- /dev/null +++ b/packages/flutter_calendar_carousel/doc/calendar5.gif diff --git a/packages/flutter_calendar_carousel/issue_template.md b/packages/flutter_calendar_carousel/issue_template.md new file mode 100644 index 0000000..751e4f3 --- /dev/null +++ b/packages/flutter_calendar_carousel/issue_template.md @@ -0,0 +1,9 @@ +### Version of flutter_calendar_carousel + +### Expected behavior + +### Actual behavior + +### flutter doctor result (run flutter doctor in terminal) + +### Steps to reproduce the behavior diff --git a/packages/flutter_calendar_carousel/lib/classes/event.dart b/packages/flutter_calendar_carousel/lib/classes/event.dart new file mode 100644 index 0000000..c852a94 --- /dev/null +++ b/packages/flutter_calendar_carousel/lib/classes/event.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; + +class Event implements EventInterface { + final DateTime date; + final String? title; + final String? description; + final String? location; + final Widget? icon; + final Widget? dot; + final int? id; + Event({ + this.id, + required this.date, + this.title, + this.description, + this.location, + this.icon, + this.dot, + }); + + @override + bool operator ==(dynamic other) { + return date == other.date && + title == other.title && + description == other.description && + location == other.location && + icon == other.icon && + dot == other.dot && + id == other.id; + } + + @override + int get hashCode => Object.hash(date, description, location, title, icon, id); + + @override + DateTime getDate() { + return date; + } + + @override + int? getId() { + return id; + } + + @override + Widget? getDot() { + return dot; + } + + @override + Widget? getIcon() { + return icon; + } + + @override + String? getTitle() { + return title; + } + + @override + String? getDescription() { + return description; + } + + @override + String? getLocation() { + return location; + } +} + +abstract class EventInterface { + DateTime getDate(); + String? getTitle(); + String? getDescription(); + String? getLocation(); + Widget? getIcon(); + Widget? getDot(); + int? getId(); +} diff --git a/packages/flutter_calendar_carousel/lib/classes/event_list.dart b/packages/flutter_calendar_carousel/lib/classes/event_list.dart new file mode 100644 index 0000000..a8c9252 --- /dev/null +++ b/packages/flutter_calendar_carousel/lib/classes/event_list.dart @@ -0,0 +1,40 @@ +class EventList<T> { + Map<DateTime, List<T>> events; + + EventList({ + required this.events, + }); + + void add(DateTime date, T event) { + final eventsOfDate = events[date]; + if (eventsOfDate == null) + events[date] = [event]; + else + eventsOfDate.add(event); + } + + void addAll(DateTime date, List<T> events) { + final eventsOfDate = this.events[date]; + if (eventsOfDate == null) + this.events[date] = events; + else + eventsOfDate.addAll(events); + } + + bool remove(DateTime date, T event) { + final eventsOfDate = events[date]; + return eventsOfDate != null ? eventsOfDate.remove(event) : false; + } + + List<T> removeAll(DateTime date) { + return events.remove(date) ?? []; + } + + void clear() { + events.clear(); + } + + List<T> getEvents(DateTime date) { + return events[date] ?? []; + } +} diff --git a/packages/flutter_calendar_carousel/lib/classes/marked_date.dart b/packages/flutter_calendar_carousel/lib/classes/marked_date.dart new file mode 100644 index 0000000..a3db2d5 --- /dev/null +++ b/packages/flutter_calendar_carousel/lib/classes/marked_date.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +class MarkedDate implements MarkedDateInterface { + final Color color; + final int? id; + final TextStyle? textStyle; + final DateTime date; + + MarkedDate({ + required this.color, + this.id, + this.textStyle, + required this.date, + }); + + @override + bool operator ==(dynamic other) { + return date == other.date && + color == other.color && + textStyle == other.textStyle && + id == other.id; + } + + @override + DateTime getDate() => this.date; + + @override + int? getId() => this.id; + + @override + Color getColor() => this.color; + + @override + TextStyle? getTextStyle() => this.textStyle; + + @override + // TODO: implement hashCode + int get hashCode => super.hashCode; +} + +abstract class MarkedDateInterface { + DateTime getDate(); + Color getColor(); + int? getId(); + TextStyle? getTextStyle(); +} diff --git a/packages/flutter_calendar_carousel/lib/classes/multiple_marked_dates.dart b/packages/flutter_calendar_carousel/lib/classes/multiple_marked_dates.dart new file mode 100644 index 0000000..3934549 --- /dev/null +++ b/packages/flutter_calendar_carousel/lib/classes/multiple_marked_dates.dart @@ -0,0 +1,86 @@ +import 'marked_date.dart'; +import 'package:flutter/material.dart'; + +class MultipleMarkedDates { + List<MarkedDate> markedDates; + + MultipleMarkedDates({required this.markedDates}); + + void add(MarkedDate markedDate) { + markedDates.add(markedDate); + } + + void addRange(MarkedDate markedDate, {int plus = 0, int minus = 0}) { + this.add(markedDate); + + if (plus > 0) { + int start = 1; + MarkedDate newAddMarkedDate; + + while (start <= plus) { + newAddMarkedDate = new MarkedDate( + color: markedDate.color, + date: markedDate.date.add(Duration(days: start)), + textStyle: markedDate.textStyle, + ); + + this.add(newAddMarkedDate); + + start += 1; + } + } + + if (minus > 0) { + int start = 1; + MarkedDate newSubMarkedDate; + + while (start <= minus) { + newSubMarkedDate = new MarkedDate( + color: markedDate.color, + date: markedDate.date.subtract(Duration(days: start)), + textStyle: markedDate.textStyle, + ); + + this.add(newSubMarkedDate); + + start += 1; + } + } + } + + void addAll(List<MarkedDate> markedDates) { + this.markedDates.addAll(markedDates); + } + + bool remove(MarkedDate markedDate) { + return markedDates.remove(markedDate); + } + + void clear() { + markedDates.clear(); + } + + bool isMarked(DateTime date) { + final results = markedDates.firstWhere((element) => element.date == date, + orElse: () => MarkedDate(color: Colors.black, date: DateTime(0))); + return results.date.year == date.year; + } + + Color getColor(DateTime date) { + final results = markedDates.firstWhere((element) => element.date == date, + orElse: () => MarkedDate(color: Colors.black, date: DateTime(0))); + return results.color; + } + + DateTime getDate(DateTime date) { + final results = markedDates.firstWhere((element) => element.date == date, + orElse: () => MarkedDate(color: Colors.black, date: DateTime(0))); + return results.date; + } + + TextStyle? getTextStyle(DateTime date) { + final results = markedDates.firstWhere((element) => element.date == date, + orElse: () => MarkedDate(color: Colors.black, date: DateTime(0))); + return results.textStyle; + } +} diff --git a/packages/flutter_calendar_carousel/lib/flutter_calendar_carousel.dart b/packages/flutter_calendar_carousel/lib/flutter_calendar_carousel.dart new file mode 100644 index 0000000..14e6cd8 --- /dev/null +++ b/packages/flutter_calendar_carousel/lib/flutter_calendar_carousel.dart @@ -0,0 +1,1214 @@ +library flutter_calendar_dooboo; + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_calendar_carousel/classes/event.dart'; +import 'package:flutter_calendar_carousel/classes/event_list.dart'; +import 'package:flutter_calendar_carousel/src/calendar_header.dart'; +import 'package:flutter_calendar_carousel/src/default_styles.dart'; +import 'package:flutter_calendar_carousel/src/weekday_row.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:intl/intl.dart' show DateFormat; + +import 'classes/multiple_marked_dates.dart'; + +export 'package:flutter_calendar_carousel/classes/event_list.dart'; + +typedef MarkedDateIconBuilder<T> = Widget? Function(T event); +typedef void OnDayLongPressed(DateTime day); + +/// This builder is called for every day in the calendar. +/// If you want to build only few custom day containers, return null for the days you want to leave with default looks +/// All characteristics like circle border are also applied to the custom day container [DayBuilder] provides. +/// (if supplied function returns null, Calendar's function will be called for [day]). +/// [isSelectable] - is between [CalendarCarousel.minSelectedDate] and [CalendarCarousel.maxSelectedDate] +/// [index] - DOES NOT equal day number! Index of the day built in current visible field +/// [isSelectedDay] - if the day is selected +/// [isToday] - if the day is similar to [DateTime.now()] +/// [isPrevMonthDay] - if the day is from previous month +/// [textStyle] - text style that would have been applied by the calendar if it was to build the day. +/// Example: if the user provided [CalendarCarousel.todayTextStyle] and [isToday] is true, +/// [CalendarCarousel.todayTextStyle] would be sent into [DayBuilder]'s [textStyle]. If user didn't +/// provide it, default [CalendarCarousel]'s textStyle would be sent. Same applies to all text styles like +/// [CalendarCarousel.prevDaysTextStyle], [CalendarCarousel.daysTextStyle] etc. +/// [isNextMonthDay] - if the day is from next month +/// [isThisMonthDay] - if the day is from next month +/// [day] - day being built. +typedef Widget? DayBuilder( + bool isSelectable, + int index, + bool isSelectedDay, + bool isToday, + bool isPrevMonthDay, + TextStyle textStyle, + bool isNextMonthDay, + bool isThisMonthDay, + DateTime day); + +/// This builder is called for every weekday container (7 times, from Mon to Sun). +/// [weekday] - weekday built, from 0 to 6. +/// [weekdayName] - string representation of the weekday (Mon, Tue, Wed, etc). +typedef Widget WeekdayBuilder(int weekday, String weekdayName); + +class CalendarCarousel<T extends EventInterface> extends StatefulWidget { + final double viewportFraction; + final TextStyle? prevDaysTextStyle; + final TextStyle? daysTextStyle; + final TextStyle? nextDaysTextStyle; + final Color prevMonthDayBorderColor; + final Color thisMonthDayBorderColor; + final Color nextMonthDayBorderColor; + final double dayPadding; + final double height; + final double width; + final TextStyle? todayTextStyle; + final Color dayButtonColor; + final Color todayBorderColor; + final Color todayButtonColor; + final DateTime? selectedDateTime; + final DateTime? targetDateTime; + final TextStyle? selectedDayTextStyle; + final Color selectedDayButtonColor; + final Color selectedDayBorderColor; + final bool? daysHaveCircularBorder; + final bool disableDayPressed; + final Function(DateTime, List<T>)? onDayPressed; + final TextStyle? weekdayTextStyle; + final Color iconColor; + final TextStyle? headerTextStyle; + final String? headerText; + final TextStyle? weekendTextStyle; + final EventList<T>? markedDatesMap; + + /// Change `makredDateWidget` when `markedDateShowIcon` is set to false. + final Widget? markedDateWidget; + + /// Change `OutlinedBorder` when `markedDateShowIcon` is set to false. + final OutlinedBorder? markedDateCustomShapeBorder; + + /// Change `TextStyle` when `markedDateShowIcon` is set to false. + final TextStyle? markedDateCustomTextStyle; + + /// Icon will overlap the [Day] widget when `markedDateShowIcon` is set to true. + /// This will also make below parameters work. + final bool markedDateShowIcon; + final Color? markedDateIconBorderColor; + final int markedDateIconMaxShown; + final double markedDateIconMargin; + final double markedDateIconOffset; + final MarkedDateIconBuilder<T>? markedDateIconBuilder; + + /// null - no indicator, true - show the total events, false - show the total of hidden events + final bool? markedDateMoreShowTotal; + final Decoration? markedDateMoreCustomDecoration; + final TextStyle? markedDateMoreCustomTextStyle; + final EdgeInsets headerMargin; + final double childAspectRatio; + final EdgeInsets weekDayMargin; + final EdgeInsets weekDayPadding; + final WeekdayBuilder? customWeekDayBuilder; + final DayBuilder? customDayBuilder; + final Color weekDayBackgroundColor; + final bool weekFormat; + final bool showWeekDays; + final bool showHeader; + final bool showHeaderButton; + final MultipleMarkedDates? multipleMarkedDates; + final Widget? leftButtonIcon; + final Widget? rightButtonIcon; + final ScrollPhysics? customGridViewPhysics; + final Function(DateTime)? onCalendarChanged; + final String locale; + final int? firstDayOfWeek; + final DateTime? minSelectedDate; + final DateTime? maxSelectedDate; + final TextStyle? inactiveDaysTextStyle; + final TextStyle? inactiveWeekendTextStyle; + final bool headerTitleTouchable; + final Function? onHeaderTitlePressed; + final Function? onLeftArrowPressed; + final Function? onRightArrowPressed; + final WeekdayFormat weekDayFormat; + final bool staticSixWeekFormat; + final bool isScrollable; + final Axis scrollDirection; + final bool showOnlyCurrentMonthDate; + final bool pageSnapping; + final OnDayLongPressed? onDayLongPressed; + final CrossAxisAlignment dayCrossAxisAlignment; + final MainAxisAlignment dayMainAxisAlignment; + final bool showIconBehindDayText; + final ScrollPhysics pageScrollPhysics; + final bool shouldShowTransform; + + CalendarCarousel({ + Key? key, + this.viewportFraction = 1.0, + this.prevDaysTextStyle, + this.daysTextStyle, + this.nextDaysTextStyle, + this.prevMonthDayBorderColor = Colors.transparent, + this.thisMonthDayBorderColor = Colors.transparent, + this.nextMonthDayBorderColor = Colors.transparent, + this.dayPadding = 2.0, + this.height = double.infinity, + this.width = double.infinity, + this.todayTextStyle, + this.dayButtonColor = Colors.transparent, + this.todayBorderColor = Colors.red, + this.todayButtonColor = Colors.red, + this.selectedDateTime, + this.targetDateTime, + this.selectedDayTextStyle, + this.selectedDayBorderColor = Colors.green, + this.selectedDayButtonColor = Colors.green, + this.daysHaveCircularBorder, + this.disableDayPressed = false, + this.onDayPressed, + this.weekdayTextStyle = const TextStyle(), + this.iconColor = Colors.blueAccent, + this.headerTextStyle, + this.headerText, + this.weekendTextStyle, + this.markedDatesMap, + this.markedDateShowIcon = false, + this.markedDateIconBorderColor, + this.markedDateIconMaxShown = 2, + this.markedDateIconMargin = 5.0, + this.markedDateIconOffset = 5.0, + this.markedDateIconBuilder, + this.markedDateMoreShowTotal, + this.markedDateMoreCustomDecoration, + this.markedDateCustomShapeBorder, + this.markedDateCustomTextStyle, + this.markedDateMoreCustomTextStyle, + this.markedDateWidget, + this.multipleMarkedDates, + this.headerMargin = const EdgeInsets.symmetric(vertical: 16.0), + this.childAspectRatio = 1.0, + this.weekDayMargin = const EdgeInsets.only(bottom: 4.0), + this.weekDayPadding = const EdgeInsets.all(0.0), + this.weekDayBackgroundColor = Colors.transparent, + this.customWeekDayBuilder, + this.customDayBuilder, + this.showWeekDays = true, + this.weekFormat = false, + this.showHeader = true, + this.showHeaderButton = true, + this.leftButtonIcon, + this.rightButtonIcon, + this.customGridViewPhysics, + this.onCalendarChanged, + this.locale = "en", + this.firstDayOfWeek, + this.minSelectedDate, + this.maxSelectedDate, + this.inactiveDaysTextStyle, + this.inactiveWeekendTextStyle, + this.headerTitleTouchable = false, + this.onHeaderTitlePressed, + this.onLeftArrowPressed, + this.onRightArrowPressed, + this.weekDayFormat = WeekdayFormat.short, + this.staticSixWeekFormat = false, + this.isScrollable = true, + this.scrollDirection = Axis.horizontal, + this.showOnlyCurrentMonthDate = false, + this.pageSnapping = false, + this.onDayLongPressed, + this.dayCrossAxisAlignment = CrossAxisAlignment.center, + this.dayMainAxisAlignment = MainAxisAlignment.center, + this.showIconBehindDayText = false, + this.pageScrollPhysics = const ScrollPhysics(), + this.shouldShowTransform = true, + }) : super(key: key); + + @override + _CalendarState<T> createState() => _CalendarState<T>(); +} + +enum WeekdayFormat { + weekdays, + standalone, + short, + standaloneShort, + narrow, + standaloneNarrow, +} + +class _CalendarState<T extends EventInterface> + extends State<CalendarCarousel<T>> { + late PageController _controller; + late List<DateTime> _dates; + late List<List<DateTime>> _weeks; + DateTime _selectedDate = DateTime.now(); + late DateTime _targetDate; + int _startWeekday = 0; + int _endWeekday = 0; + late DateFormat _localeDate; + int _pageNum = 0; + late DateTime minDate; + late DateTime maxDate; + + /// When FIRSTDAYOFWEEK is 0 in dart-intl, it represents Monday. However it is the second day in the arrays of Weekdays. + /// Therefore we need to add 1 modulo 7 to pick the right weekday from intl. (cf. [GlobalMaterialLocalizations]) + late int firstDayOfWeek; + + /// If the setState called from this class, don't reload the selectedDate, but it should reload selected date if called from external class + + @override + initState() { + super.initState(); + initializeDateFormatting(); + + minDate = widget.minSelectedDate ?? DateTime(2018); + maxDate = widget.maxSelectedDate ?? + DateTime( + DateTime.now().year + 1, DateTime.now().month, DateTime.now().day); + + final selectedDateTime = widget.selectedDateTime; + if (selectedDateTime != null) _selectedDate = selectedDateTime; + + _init(); + + /// setup pageController + _controller = PageController( + initialPage: this._pageNum, + keepPage: true, + viewportFraction: widget.viewportFraction, + + /// width percentage + ); + + _localeDate = DateFormat.yMMM(widget.locale); + firstDayOfWeek = widget.firstDayOfWeek ?? + (_localeDate.dateSymbols.FIRSTDAYOFWEEK + 1) % 7; + + _setDate(); + } + + @override + void didUpdateWidget(CalendarCarousel<T> oldWidget) { + if (widget.targetDateTime != null && widget.targetDateTime != _targetDate) { + _init(); + _setDate(_pageNum); + } + + super.didUpdateWidget(oldWidget); + } + + @override + dispose() { + _controller.dispose(); + super.dispose(); + } + + _init() { + final targetDateTime = widget.targetDateTime; + if (targetDateTime != null) { + if (targetDateTime.difference(minDate).inDays < 0) { + _targetDate = minDate; + } else if (targetDateTime.difference(maxDate).inDays > 0) { + _targetDate = maxDate; + } else { + _targetDate = targetDateTime; + } + } else { + _targetDate = _selectedDate; + } + if (widget.weekFormat) { + _pageNum = _targetDate.difference(_firstDayOfWeek(minDate)).inDays ~/ 7; + } else { + _pageNum = (_targetDate.year - minDate.year) * 12 + + _targetDate.month - + minDate.month; + } + } + + @override + Widget build(BuildContext context) { + final headerText = widget.headerText; + return Container( + width: widget.width, + height: widget.height, + child: Column( + children: <Widget>[ + CalendarHeader( + showHeader: widget.showHeader, + headerMargin: widget.headerMargin, + headerTitle: headerText != null + ? headerText + : widget.weekFormat + ? '${_localeDate.format(this._weeks[this._pageNum].first)}' + : '${_localeDate.format(this._dates[this._pageNum])}', + headerTextStyle: widget.headerTextStyle, + showHeaderButtons: widget.showHeaderButton, + headerIconColor: widget.iconColor, + leftButtonIcon: widget.leftButtonIcon, + rightButtonIcon: widget.rightButtonIcon, + onLeftButtonPressed: () { + widget.onLeftArrowPressed?.call(); + + if (this._pageNum > 0) _setDate(this._pageNum - 1); + }, + onRightButtonPressed: () { + widget.onRightArrowPressed?.call(); + + if (widget.weekFormat) { + if (this._weeks.length - 1 > this._pageNum) + _setDate(this._pageNum + 1); + } else { + if (this._dates.length - 1 > this._pageNum) + _setDate(this._pageNum + 1); + } + }, + onHeaderTitlePressed: widget.headerTitleTouchable + ? () { + final onHeaderTitlePressed = widget.onHeaderTitlePressed; + if (onHeaderTitlePressed != null) { + onHeaderTitlePressed(); + } else { + _selectDateFromPicker(); + } + } + : null, + ), + WeekdayRow( + firstDayOfWeek, + widget.customWeekDayBuilder, + showWeekdays: widget.showWeekDays, + weekdayFormat: widget.weekDayFormat, + weekdayMargin: widget.weekDayMargin, + weekdayPadding: widget.weekDayPadding, + weekdayBackgroundColor: widget.weekDayBackgroundColor, + weekdayTextStyle: widget.weekdayTextStyle, + localeDate: _localeDate, + ), + Expanded( + child: PageView.builder( + itemCount: + widget.weekFormat ? this._weeks.length : this._dates.length, + physics: widget.isScrollable + ? widget.pageScrollPhysics + : NeverScrollableScrollPhysics(), + scrollDirection: widget.scrollDirection, + onPageChanged: (index) { + this._setDate(index); + }, + controller: _controller, + itemBuilder: (context, index) { + return widget.weekFormat ? weekBuilder(index) : builder(index); + }, + pageSnapping: widget.pageSnapping, + )), + ], + ), + ); + } + + Widget getDefaultDayContainer( + bool isSelectable, + int index, + bool isSelectedDay, + bool isToday, + bool isPrevMonthDay, + TextStyle? textStyle, + TextStyle defaultTextStyle, + bool isNextMonthDay, + bool isThisMonthDay, + DateTime now, + ) { + return Container( + width: double.infinity, + height: double.infinity, + child: Row( + crossAxisAlignment: widget.dayCrossAxisAlignment, + mainAxisAlignment: widget.dayMainAxisAlignment, + children: <Widget>[ + DefaultTextStyle( + style: getDefaultDayStyle( + isSelectable, + index, + isSelectedDay, + isToday, + isPrevMonthDay, + textStyle, + defaultTextStyle, + isNextMonthDay, + isThisMonthDay), + child: Text( + '${now.day}', + semanticsLabel: now.day.toString(), + style: getDayStyle( + isSelectable, + index, + isSelectedDay, + isToday, + isPrevMonthDay, + textStyle, + defaultTextStyle, + isNextMonthDay, + isThisMonthDay, + now), + maxLines: 1, + ), + ), + ], + ), + ); + } + + Widget renderDay( + bool isSelectable, + int index, + bool isSelectedDay, + bool isToday, + bool isPrevMonthDay, + TextStyle? textStyle, + TextStyle defaultTextStyle, + bool isNextMonthDay, + bool isThisMonthDay, + DateTime now, + ) { + // If day is in Multiple selection mode, get its color + bool isMultipleMarked = widget.multipleMarkedDates?.isMarked(now) ?? false; + Color? multipleMarkedColor = widget.multipleMarkedDates?.getColor(now); + + final markedDatesMap = widget.markedDatesMap; + return Container( + margin: EdgeInsets.all(widget.dayPadding), + child: GestureDetector( + onLongPress: () => _onDayLongPressed(now), + child: TextButton( + style: TextButton.styleFrom( + shape: widget.markedDateCustomShapeBorder != null && + markedDatesMap != null && + markedDatesMap.getEvents(now).length > 0 + ? widget.markedDateCustomShapeBorder + : widget.daysHaveCircularBorder == null + ? CircleBorder() + : widget.daysHaveCircularBorder ?? false + ? CircleBorder( + side: BorderSide( + color: isSelectedDay + ? widget.selectedDayBorderColor + : isToday + ? widget.todayBorderColor + : isPrevMonthDay + ? widget.prevMonthDayBorderColor + : isNextMonthDay + ? widget.nextMonthDayBorderColor + : widget.thisMonthDayBorderColor, + ), + ) + : RoundedRectangleBorder( + side: BorderSide( + color: isSelectedDay + ? widget.selectedDayBorderColor + : isToday + ? widget.todayBorderColor + : isPrevMonthDay + ? widget.prevMonthDayBorderColor + : isNextMonthDay + ? widget.nextMonthDayBorderColor + : widget.thisMonthDayBorderColor, + ), + ), + backgroundColor: isSelectedDay + ? widget.selectedDayButtonColor + : isToday + ? widget.todayButtonColor + + // If day is in Multiple selection mode, apply a different color + : isMultipleMarked + ? multipleMarkedColor + : widget.dayButtonColor, + padding: EdgeInsets.all(widget.dayPadding), + ), + onPressed: widget.disableDayPressed ? null : () => _onDayPressed(now), + child: Stack( + children: widget.showIconBehindDayText + ? <Widget>[ + widget.markedDatesMap != null + ? _renderMarkedMapContainer(now) + : Container(), + getDayContainer( + isSelectable, + index, + isSelectedDay, + isToday, + isPrevMonthDay, + textStyle, + defaultTextStyle, + isNextMonthDay, + isThisMonthDay, + now), + ] + : <Widget>[ + getDayContainer( + isSelectable, + index, + isSelectedDay, + isToday, + isPrevMonthDay, + textStyle, + defaultTextStyle, + isNextMonthDay, + isThisMonthDay, + now), + widget.markedDatesMap != null + ? _renderMarkedMapContainer(now) + : Container(), + ], + ), + ), + ), + ); + } + + AnimatedBuilder builder(int slideIndex) { + _startWeekday = _dates[slideIndex].weekday - firstDayOfWeek; + if (_startWeekday == 7) { + _startWeekday = 0; + } + _endWeekday = + DateTime(_dates[slideIndex].year, _dates[slideIndex].month + 1, 1) + .weekday - + firstDayOfWeek; + double screenWidth = MediaQuery.of(context).size.width; + int totalItemCount = widget.staticSixWeekFormat + ? 42 + : DateTime( + _dates[slideIndex].year, + _dates[slideIndex].month + 1, + 0, + ).day + + _startWeekday + + (7 - _endWeekday); + int year = _dates[slideIndex].year; + int month = _dates[slideIndex].month; + + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + if (!widget.shouldShowTransform) { + return child!; + } + double value = 1.0; + if (_controller.position.haveDimensions) { + value = _controller.page! - slideIndex; + value = (1 - (value.abs() * .5)).clamp(0.0, 1.0); + } + + return Center( + child: SizedBox( + height: Curves.easeOut.transform(value) * widget.height, + width: Curves.easeOut.transform(value) * screenWidth, + child: child, + ), + ); + }, + child: Stack( + children: <Widget>[ + Positioned( + child: Container( + width: double.infinity, + height: double.infinity, + child: GridView.count( + physics: widget.customGridViewPhysics, + crossAxisCount: 7, + childAspectRatio: widget.childAspectRatio, + padding: EdgeInsets.zero, + children: List.generate(totalItemCount, + + /// last day of month + weekday + (index) { + final selectedDateTime = widget.selectedDateTime; + bool isToday = + DateTime.now().day == index + 1 - _startWeekday && + DateTime.now().month == month && + DateTime.now().year == year; + bool isSelectedDay = selectedDateTime != null && + selectedDateTime.year == year && + selectedDateTime.month == month && + selectedDateTime.day == index + 1 - _startWeekday; + bool isPrevMonthDay = index < _startWeekday; + bool isNextMonthDay = index >= + (DateTime(year, month + 1, 0).day) + _startWeekday; + bool isThisMonthDay = !isPrevMonthDay && !isNextMonthDay; + + DateTime now = DateTime(year, month, 1); + TextStyle? textStyle; + TextStyle defaultTextStyle; + if (isPrevMonthDay && !widget.showOnlyCurrentMonthDate) { + now = now.subtract(Duration(days: _startWeekday - index)); + textStyle = widget.prevDaysTextStyle; + defaultTextStyle = defaultPrevDaysTextStyle; + } else if (isThisMonthDay) { + now = DateTime(year, month, index + 1 - _startWeekday); + textStyle = isSelectedDay + ? widget.selectedDayTextStyle + : isToday + ? widget.todayTextStyle + : widget.daysTextStyle; + defaultTextStyle = isSelectedDay + ? defaultSelectedDayTextStyle + : isToday + ? defaultTodayTextStyle + : defaultDaysTextStyle; + } else if (!widget.showOnlyCurrentMonthDate) { + now = DateTime(year, month, index + 1 - _startWeekday); + textStyle = widget.nextDaysTextStyle; + defaultTextStyle = defaultNextDaysTextStyle; + } else { + return Container(); + } + final markedDatesMap = widget.markedDatesMap; + if (widget.markedDateCustomTextStyle != null && + markedDatesMap != null && + markedDatesMap.getEvents(now).length > 0) { + textStyle = widget.markedDateCustomTextStyle; + } + bool isSelectable = true; + if (now.millisecondsSinceEpoch < + minDate.millisecondsSinceEpoch) + isSelectable = false; + else if (now.millisecondsSinceEpoch > + maxDate.millisecondsSinceEpoch) isSelectable = false; + return renderDay( + isSelectable, + index, + isSelectedDay, + isToday, + isPrevMonthDay, + textStyle, + defaultTextStyle, + isNextMonthDay, + isThisMonthDay, + now); + }), + ), + ), + ), + ], + ), + ); + } + + AnimatedBuilder weekBuilder(int slideIndex) { + double screenWidth = MediaQuery.of(context).size.width; + List<DateTime> weekDays = _weeks[slideIndex]; + + weekDays = weekDays + .map((weekDay) => weekDay.add(Duration(days: firstDayOfWeek))) + .toList(); + + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + double value = 1.0; + if (_controller.position.haveDimensions) { + value = _controller.page! - slideIndex; + value = (1 - (value.abs() * .5)).clamp(0.0, 1.0); + } + + return Center( + child: SizedBox( + height: Curves.easeOut.transform(value) * widget.height, + width: Curves.easeOut.transform(value) * screenWidth, + child: child, + ), + ); + }, + child: Stack( + children: <Widget>[ + Positioned( + child: Container( + width: double.infinity, + height: double.infinity, + child: GridView.count( + physics: widget.customGridViewPhysics, + crossAxisCount: 7, + childAspectRatio: widget.childAspectRatio, + padding: EdgeInsets.zero, + children: List.generate(weekDays.length, (index) { + /// last day of month + weekday + bool isToday = weekDays[index].day == DateTime.now().day && + weekDays[index].month == DateTime.now().month && + weekDays[index].year == DateTime.now().year; + bool isSelectedDay = + this._selectedDate.year == weekDays[index].year && + this._selectedDate.month == weekDays[index].month && + this._selectedDate.day == weekDays[index].day; + bool isPrevMonthDay = + weekDays[index].month < this._targetDate.month; + bool isNextMonthDay = + weekDays[index].month > this._targetDate.month; + bool isThisMonthDay = !isPrevMonthDay && !isNextMonthDay; + + DateTime now = DateTime(weekDays[index].year, + weekDays[index].month, weekDays[index].day); + TextStyle? textStyle; + TextStyle defaultTextStyle; + if (isPrevMonthDay && !widget.showOnlyCurrentMonthDate) { + textStyle = widget.prevDaysTextStyle; + defaultTextStyle = defaultPrevDaysTextStyle; + } else if (isThisMonthDay) { + textStyle = isSelectedDay + ? widget.selectedDayTextStyle + : isToday + ? widget.todayTextStyle + : widget.daysTextStyle; + defaultTextStyle = isSelectedDay + ? defaultSelectedDayTextStyle + : isToday + ? defaultTodayTextStyle + : defaultDaysTextStyle; + } else if (!widget.showOnlyCurrentMonthDate) { + textStyle = widget.nextDaysTextStyle; + defaultTextStyle = defaultNextDaysTextStyle; + } else { + return Container(); + } + bool isSelectable = true; + if (now.millisecondsSinceEpoch < + minDate.millisecondsSinceEpoch) + isSelectable = false; + else if (now.millisecondsSinceEpoch > + maxDate.millisecondsSinceEpoch) isSelectable = false; + return renderDay( + isSelectable, + index, + isSelectedDay, + isToday, + isPrevMonthDay, + textStyle, + defaultTextStyle, + isNextMonthDay, + isThisMonthDay, + now); + }), + ), + ), + ), + ], + )); + } + + List<DateTime> _getDaysInWeek([DateTime? selectedDate]) { + if (selectedDate == null) selectedDate = new DateTime.now(); + + var firstDayOfCurrentWeek = _firstDayOfWeek(selectedDate); + var lastDayOfCurrentWeek = _lastDayOfWeek(selectedDate); + + return _daysInRange(firstDayOfCurrentWeek, lastDayOfCurrentWeek).toList(); + } + + DateTime _firstDayOfWeek(DateTime date) { + var day = _createUTCMiddayDateTime(date); + return day.subtract(new Duration(days: date.weekday % 7)); + } + + DateTime _lastDayOfWeek(DateTime date) { + var day = _createUTCMiddayDateTime(date); + return day.add(new Duration(days: 7 - day.weekday % 7)); + } + + DateTime _createUTCMiddayDateTime(DateTime date) { + // Magic const: 12 is to maintain compatibility with date_utils + return new DateTime.utc(date.year, date.month, date.day, 12, 0, 0); + } + + Iterable<DateTime> _daysInRange(DateTime start, DateTime end) { + var offset = start.timeZoneOffset; + + return List<int>.generate(end.difference(start).inDays, (i) => i + 1) + .map((int i) { + var d = start.add(Duration(days: i - 1)); + + var timeZoneDiff = d.timeZoneOffset - offset; + if (timeZoneDiff.inSeconds != 0) { + offset = d.timeZoneOffset; + d = d.subtract(new Duration(seconds: timeZoneDiff.inSeconds)); + } + return d; + }); + } + + void _onDayLongPressed(DateTime picked) { + widget.onDayLongPressed?.call(picked); + } + + void _onDayPressed(DateTime picked) { + if (picked.millisecondsSinceEpoch < minDate.millisecondsSinceEpoch) return; + if (picked.millisecondsSinceEpoch > maxDate.millisecondsSinceEpoch) return; + + setState(() { + _selectedDate = picked; + }); + widget.onDayPressed + ?.call(picked, widget.markedDatesMap?.getEvents(picked) ?? const []); + } + + Future<Null> _selectDateFromPicker() async { + DateTime? selected = await showDatePicker( + context: context, + initialDate: _selectedDate, + firstDate: minDate, + lastDate: maxDate, + ); + + if (selected != null) { + // updating selected date range based on selected week + setState(() { + _selectedDate = selected; + }); + widget.onDayPressed?.call( + selected, widget.markedDatesMap?.getEvents(selected) ?? const []); + } + } + + void _setDatesAndWeeks() { + /// Setup default calendar format + List<DateTime> date = []; + int currentDateIndex = 0; + for (int _cnt = 0; + 0 >= + DateTime(minDate.year, minDate.month + _cnt) + .difference(DateTime(maxDate.year, maxDate.month)) + .inDays; + _cnt++) { + date.add(DateTime(minDate.year, minDate.month + _cnt, 1)); + if (0 == + date.last + .difference( + DateTime(this._targetDate.year, this._targetDate.month)) + .inDays) { + currentDateIndex = _cnt; + } + } + + /// Setup week-only format + List<List<DateTime>> week = []; + for (int _cnt = 0; + 0 >= + minDate + .add(Duration(days: 7 * _cnt)) + .difference(maxDate.add(Duration(days: 7))) + .inDays; + _cnt++) { + week.add(_getDaysInWeek(minDate.add(new Duration(days: 7 * _cnt)))); + } + + _startWeekday = date[currentDateIndex].weekday - firstDayOfWeek; + /*if (widget.showOnlyCurrentMonthDate) { + _startWeekday--; + }*/ + if (/*widget.showOnlyCurrentMonthDate && */ _startWeekday == 7) { + _startWeekday = 0; + } + _endWeekday = DateTime(date[currentDateIndex].year, + date[currentDateIndex].month + 1, 1) + .weekday - + firstDayOfWeek; + this._dates = date; + this._weeks = week; +// this._selectedDate = widget.selectedDateTime != null +// ? widget.selectedDateTime +// : DateTime.now(); + } + + void _setDate([int page = -1]) { + if (page == -1) { + setState(() { + _setDatesAndWeeks(); + }); + } else { + if (widget.weekFormat) { + setState(() { + this._pageNum = page; + this._targetDate = this._weeks[page].first; + }); + + _controller.animateToPage(page, + duration: Duration(milliseconds: 1), curve: Threshold(0.0)); + } else { + setState(() { + this._pageNum = page; + this._targetDate = this._dates[page]; + _startWeekday = _dates[page].weekday - firstDayOfWeek; + _endWeekday = _lastDayOfWeek(_dates[page]).weekday - firstDayOfWeek; + }); + _controller.animateToPage(page, + duration: Duration(milliseconds: 1), curve: Threshold(0.0)); + } + + //call callback + final onCalendarChanged = widget.onCalendarChanged; + if (onCalendarChanged != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + onCalendarChanged(!widget.weekFormat + ? this._dates[page] + : this._weeks[page][firstDayOfWeek]); + }); + } + } + } + + Widget _renderMarkedMapContainer(DateTime now) { + if (widget.markedDateShowIcon) { + return Stack( + children: _renderMarkedMap(now), + ); + } else { + return Container( + height: double.infinity, + padding: EdgeInsets.only(bottom: 4.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: _renderMarkedMap(now), + ), + ); + } + } + + List<Widget> _renderMarkedMap(DateTime now) { + final markedEvents = widget.markedDatesMap?.getEvents(now) ?? []; + final markedDateIconBuilder = widget.markedDateIconBuilder; + final markedDateWidget = widget.markedDateWidget; + final markedDateMoreShowTotal = widget.markedDateMoreShowTotal; + final markedDateMoreCustomTextStyle = widget.markedDateMoreCustomTextStyle; + final markedDateIconMargin = widget.markedDateIconMargin; + final markedDateShowIcon = widget.markedDateShowIcon; + final markedDateIconMaxShown = widget.markedDateIconMaxShown; + final markedDateIconOffset = widget.markedDateIconOffset; + final markedDateMoreCustomDecoration = + widget.markedDateMoreCustomDecoration; + + if (markedEvents.length > 0) { + List<Widget> tmp = []; + int count = 0; + int eventIndex = 0; + double offset = 0.0; + double padding = markedDateIconMargin; + markedEvents.forEach((T event) { + if (markedDateShowIcon) { + if (tmp.length > 0 && tmp.length < markedDateIconMaxShown) { + offset += markedDateIconOffset; + } + if (tmp.length < markedDateIconMaxShown && + markedDateIconBuilder != null) { + tmp.add(Center( + child: new Container( + padding: EdgeInsets.only( + top: padding + offset, + left: padding + offset, + right: padding - offset, + bottom: padding - offset, + ), + width: double.infinity, + height: double.infinity, + child: markedDateIconBuilder(event), + ))); + } else { + count++; + } + if (count > 0 && markedDateMoreShowTotal != null) { + tmp.add( + Positioned( + bottom: 0.0, + right: 0.0, + child: Container( + padding: EdgeInsets.all(4.0), + width: markedDateMoreShowTotal ? 18 : null, + height: markedDateMoreShowTotal ? 18 : null, + decoration: markedDateMoreCustomDecoration == null + ? new BoxDecoration( + color: Colors.red, + borderRadius: + BorderRadius.all(Radius.circular(1000.0)), + ) + : markedDateMoreCustomDecoration, + child: Center( + child: Text( + markedDateMoreShowTotal + ? (count + markedDateIconMaxShown).toString() + : (count.toString() + '+'), + semanticsLabel: markedDateMoreShowTotal + ? (count + markedDateIconMaxShown).toString() + : (count.toString() + '+'), + style: markedDateMoreCustomTextStyle == null + ? TextStyle( + fontSize: 9.0, + color: Colors.white, + fontWeight: FontWeight.normal) + : markedDateMoreCustomTextStyle, + ), + ), + ), + ), + ); + } + } else { + //max 5 dots + if (eventIndex < 5) { + Widget? widget; + + if (markedDateIconBuilder != null) { + widget = markedDateIconBuilder(event); + } + + if (widget != null) { + tmp.add(widget); + } else { + final dot = event.getDot(); + if (dot != null) { + tmp.add(dot); + } else if (markedDateWidget != null) { + tmp.add(markedDateWidget); + } else { + tmp.add(defaultMarkedDateWidget); + } + } + } + } + + eventIndex++; + }); + return tmp; + } + return []; + } + + TextStyle getDefaultDayStyle( + bool isSelectable, + int index, + bool isSelectedDay, + bool isToday, + bool isPrevMonthDay, + TextStyle? textStyle, + TextStyle defaultTextStyle, + bool isNextMonthDay, + bool isThisMonthDay, + ) { + return !isSelectable + ? defaultInactiveDaysTextStyle + : (_localeDate.dateSymbols.WEEKENDRANGE + .contains((index - 1 + firstDayOfWeek) % 7)) && + !isSelectedDay && + !isToday + ? (isPrevMonthDay + ? defaultPrevDaysTextStyle + : isNextMonthDay + ? defaultNextDaysTextStyle + : isSelectable + ? defaultWeekendTextStyle + : defaultInactiveWeekendTextStyle) + : isToday + ? defaultTodayTextStyle + : isSelectable && textStyle != null + ? textStyle + : defaultTextStyle; + } + + TextStyle? getDayStyle( + bool isSelectable, + int index, + bool isSelectedDay, + bool isToday, + bool isPrevMonthDay, + TextStyle? textStyle, + TextStyle defaultTextStyle, + bool isNextMonthDay, + bool isThisMonthDay, + DateTime now) { + // If day is in multiple selection get its style(if available) + bool isMultipleMarked = widget.multipleMarkedDates?.isMarked(now) ?? false; + TextStyle? mutipleMarkedTextStyle = + widget.multipleMarkedDates?.getTextStyle(now); + + return isSelectedDay && widget.selectedDayTextStyle != null + ? widget.selectedDayTextStyle + : isMultipleMarked + ? mutipleMarkedTextStyle + : (_localeDate.dateSymbols.WEEKENDRANGE + .contains((index - 1 + firstDayOfWeek) % 7)) && + !isSelectedDay && + isThisMonthDay && + !isToday + ? (isSelectable + ? widget.weekendTextStyle + : widget.inactiveWeekendTextStyle) + : !isSelectable + ? widget.inactiveDaysTextStyle + : isPrevMonthDay + ? widget.prevDaysTextStyle + : isNextMonthDay + ? widget.nextDaysTextStyle + : isToday + ? widget.todayTextStyle + : widget.daysTextStyle; + } + + Widget getDayContainer( + bool isSelectable, + int index, + bool isSelectedDay, + bool isToday, + bool isPrevMonthDay, + TextStyle? textStyle, + TextStyle defaultTextStyle, + bool isNextMonthDay, + bool isThisMonthDay, + DateTime now) { + final customDayBuilder = widget.customDayBuilder; + + Widget? dayContainer; + if (customDayBuilder != null) { + final appTextStyle = DefaultTextStyle.of(context).style; + + final dayStyle = getDayStyle( + isSelectable, + index, + isSelectedDay, + isToday, + isPrevMonthDay, + textStyle, + defaultTextStyle, + isNextMonthDay, + isThisMonthDay, + now, + ); + + final styleForBuilder = appTextStyle.merge(dayStyle); + + dayContainer = customDayBuilder( + isSelectable, + index, + isSelectedDay, + isToday, + isPrevMonthDay, + styleForBuilder, + isNextMonthDay, + isThisMonthDay, + now); + } + + return dayContainer ?? + getDefaultDayContainer( + isSelectable, + index, + isSelectedDay, + isToday, + isPrevMonthDay, + textStyle, + defaultTextStyle, + isNextMonthDay, + isThisMonthDay, + now, + ); + } +} diff --git a/packages/flutter_calendar_carousel/lib/src/calendar_header.dart b/packages/flutter_calendar_carousel/lib/src/calendar_header.dart new file mode 100644 index 0000000..7fcfd81 --- /dev/null +++ b/packages/flutter_calendar_carousel/lib/src/calendar_header.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'default_styles.dart' show defaultHeaderTextStyle; + +class CalendarHeader extends StatelessWidget { + /// Passing in values for [leftButtonIcon] or [rightButtonIcon] will override [headerIconColor] + CalendarHeader( + {required this.headerTitle, + this.headerMargin, + required this.showHeader, + this.headerTextStyle, + this.showHeaderButtons = true, + this.headerIconColor, + this.leftButtonIcon, + this.rightButtonIcon, + required this.onLeftButtonPressed, + required this.onRightButtonPressed, + this.onHeaderTitlePressed}) + : isTitleTouchable = onHeaderTitlePressed != null; + + final String headerTitle; + final EdgeInsetsGeometry? headerMargin; + final bool showHeader; + final TextStyle? headerTextStyle; + final bool showHeaderButtons; + final Color? headerIconColor; + final Widget? leftButtonIcon; + final Widget? rightButtonIcon; + final VoidCallback onLeftButtonPressed; + final VoidCallback onRightButtonPressed; + final bool isTitleTouchable; + final VoidCallback? onHeaderTitlePressed; + + TextStyle get getTextStyle => headerTextStyle ?? defaultHeaderTextStyle; + + Widget _leftButton() => IconButton( + onPressed: onLeftButtonPressed, + icon: + leftButtonIcon ?? Icon(Icons.chevron_left, color: headerIconColor), + ); + + Widget _rightButton() => IconButton( + onPressed: onRightButtonPressed, + icon: rightButtonIcon ?? + Icon(Icons.chevron_right, color: headerIconColor), + ); + + Widget _headerTouchable() => TextButton( + onPressed: onHeaderTitlePressed, + child: Text( + headerTitle, + semanticsLabel: headerTitle, + style: getTextStyle, + ), + ); + + @override + Widget build(BuildContext context) => showHeader + ? Container( + margin: headerMargin, + child: DefaultTextStyle( + style: getTextStyle, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: <Widget>[ + showHeaderButtons ? _leftButton() : Container(), + isTitleTouchable + ? _headerTouchable() + : Text(headerTitle, style: getTextStyle), + showHeaderButtons ? _rightButton() : Container(), + ])), + ) + : Container(); +} diff --git a/packages/flutter_calendar_carousel/lib/src/default_styles.dart b/packages/flutter_calendar_carousel/lib/src/default_styles.dart new file mode 100644 index 0000000..cfeddc9 --- /dev/null +++ b/packages/flutter_calendar_carousel/lib/src/default_styles.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +const TextStyle defaultHeaderTextStyle = const TextStyle( + fontSize: 20.0, + color: Colors.blue, +); +const TextStyle defaultPrevDaysTextStyle = const TextStyle( + color: Colors.grey, + fontSize: 14.0, +); +const TextStyle defaultNextDaysTextStyle = const TextStyle( + color: Colors.grey, + fontSize: 14.0, +); +const TextStyle defaultDaysTextStyle = const TextStyle( + color: Colors.black, + fontSize: 14.0, +); +const TextStyle defaultTodayTextStyle = const TextStyle( + color: Colors.white, + fontSize: 14.0, +); +const TextStyle defaultSelectedDayTextStyle = const TextStyle( + color: Colors.white, + fontSize: 14.0, +); +const TextStyle defaultWeekdayTextStyle = const TextStyle( + color: Colors.deepOrange, + fontSize: 14.0, +); +const TextStyle defaultWeekendTextStyle = const TextStyle( + color: Colors.pinkAccent, + fontSize: 14.0, +); +const TextStyle defaultInactiveDaysTextStyle = const TextStyle( + color: Colors.black38, + fontSize: 14.0, +); +final TextStyle defaultInactiveWeekendTextStyle = TextStyle( + color: Colors.pinkAccent.withOpacity(0.6), + fontSize: 14.0, +); +final Widget defaultMarkedDateWidget = Container( + margin: EdgeInsets.symmetric(horizontal: 1.0), + color: Colors.blueAccent, + height: 4.0, + width: 4.0, +); diff --git a/packages/flutter_calendar_carousel/lib/src/weekday_row.dart b/packages/flutter_calendar_carousel/lib/src/weekday_row.dart new file mode 100644 index 0000000..48f5b3a --- /dev/null +++ b/packages/flutter_calendar_carousel/lib/src/weekday_row.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_calendar_carousel/flutter_calendar_carousel.dart'; +import 'package:flutter_calendar_carousel/src/default_styles.dart' + show defaultWeekdayTextStyle; +import 'package:intl/intl.dart'; + +class WeekdayRow extends StatelessWidget { + WeekdayRow(this.firstDayOfWeek, this.customWeekdayBuilder, + {required this.showWeekdays, + required this.weekdayFormat, + required this.weekdayMargin, + required this.weekdayPadding, + required this.weekdayBackgroundColor, + required this.weekdayTextStyle, + required this.localeDate}); + + final WeekdayBuilder? customWeekdayBuilder; + final bool showWeekdays; + final WeekdayFormat weekdayFormat; + final EdgeInsets weekdayMargin; + final EdgeInsets weekdayPadding; + final Color weekdayBackgroundColor; + final TextStyle? weekdayTextStyle; + final DateFormat localeDate; + final int firstDayOfWeek; + + Widget _weekdayContainer(int weekday, String weekDayName) { + final customWeekdayBuilder = this.customWeekdayBuilder; + return customWeekdayBuilder != null + ? customWeekdayBuilder(weekday, weekDayName) + : Expanded( + child: Container( + decoration: BoxDecoration( + border: Border.all(color: weekdayBackgroundColor), + color: weekdayBackgroundColor, + ), + margin: weekdayMargin, + padding: weekdayPadding, + child: Center( + child: DefaultTextStyle( + style: defaultWeekdayTextStyle, + child: Text( + weekDayName, + semanticsLabel: weekDayName, + style: weekdayTextStyle, + ), + ), + ), + )); + } + +// List<Widget> _generateWeekdays() { +// switch (weekdayFormat) { +// case WeekdayFormat.weekdays: +// return localeDate.dateSymbols.WEEKDAYS +// .map<Widget>(_weekdayContainer) +// .toList(); +// case WeekdayFormat.standalone: +// return localeDate.dateSymbols.STANDALONEWEEKDAYS +// .map<Widget>(_weekdayContainer) +// .toList(); +// case WeekdayFormat.short: +// return localeDate.dateSymbols.SHORTWEEKDAYS +// .map<Widget>(_weekdayContainer) +// .toList(); +// case WeekdayFormat.standaloneShort: +// return localeDate.dateSymbols.STANDALONESHORTWEEKDAYS +// .map<Widget>(_weekdayContainer) +// .toList(); +// case WeekdayFormat.narrow: +// return localeDate.dateSymbols.NARROWWEEKDAYS +// .map<Widget>(_weekdayContainer) +// .toList(); +// case WeekdayFormat.standaloneNarrow: +// return localeDate.dateSymbols.STANDALONENARROWWEEKDAYS +// .map<Widget>(_weekdayContainer) +// .toList(); +// default: +// return localeDate.dateSymbols.STANDALONEWEEKDAYS +// .map<Widget>(_weekdayContainer) +// .toList(); +// } +// } + + // TODO - locale issues + List<Widget> _renderWeekDays() { + List<Widget> list = []; + + /// because of number of days in a week is 7, so it would be easier to count it til 7. + for (var i = firstDayOfWeek, count = 0; + count < 7; + i = (i + 1) % 7, count++) { + String weekDay; + + switch (weekdayFormat) { + case WeekdayFormat.weekdays: + weekDay = localeDate.dateSymbols.WEEKDAYS[i]; + break; + case WeekdayFormat.standalone: + weekDay = localeDate.dateSymbols.STANDALONEWEEKDAYS[i]; + break; + case WeekdayFormat.short: + weekDay = localeDate.dateSymbols.SHORTWEEKDAYS[i]; + break; + case WeekdayFormat.standaloneShort: + weekDay = localeDate.dateSymbols.STANDALONESHORTWEEKDAYS[i]; + break; + case WeekdayFormat.narrow: + weekDay = localeDate.dateSymbols.NARROWWEEKDAYS[i]; + break; + case WeekdayFormat.standaloneNarrow: + weekDay = localeDate.dateSymbols.STANDALONENARROWWEEKDAYS[i]; + break; + default: + weekDay = localeDate.dateSymbols.STANDALONEWEEKDAYS[i]; + break; + } + list.add(_weekdayContainer(count, weekDay)); + } + + return list; + } + + @override + Widget build(BuildContext context) => showWeekdays + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _renderWeekDays(), + ) + : Container(); +} diff --git a/packages/flutter_calendar_carousel/pubspec.lock b/packages/flutter_calendar_carousel/pubspec.lock new file mode 100644 index 0000000..b1a64dc --- /dev/null +++ b/packages/flutter_calendar_carousel/pubspec.lock @@ -0,0 +1,220 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7e108028e3d258667d079986da8c0bc32da4cb57431c2af03b1dc1038621a9dc" + url: "https://pub.dev" + source: hosted + version: "9.0.13" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: b06739349ec2477e943055aea30172c5c7000225f79dad4702e2ec0eda79a6ff + url: "https://pub.dev" + source: hosted + version: "1.0.5" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" +sdks: + dart: ">=3.2.0-194.0.dev <4.0.0" diff --git a/packages/flutter_calendar_carousel/pubspec.yaml b/packages/flutter_calendar_carousel/pubspec.yaml new file mode 100644 index 0000000..9258aa7 --- /dev/null +++ b/packages/flutter_calendar_carousel/pubspec.yaml @@ -0,0 +1,53 @@ +name: flutter_calendar_carousel +description: Calendar widget for flutter that is swipeable. This widget can help you build customizable calendar with scrollable actions. +version: 2.4.3 +homepage: https://github.com/dooboolab/flutter_calendar_carousel + +environment: + sdk: ">=2.14.0 < 4.0.0" + +dependencies: + intl: ^0.19.0 + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://www.dartlang.org/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.io/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.io/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.io/custom-fonts/#from-packages diff --git a/packages/flutter_calendar_carousel/renovate.json b/packages/flutter_calendar_carousel/renovate.json new file mode 100644 index 0000000..39a2b6e --- /dev/null +++ b/packages/flutter_calendar_carousel/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ] +} diff --git a/packages/flutter_calendar_carousel/test/flutter_calendar_carousel_test.dart b/packages/flutter_calendar_carousel/test/flutter_calendar_carousel_test.dart new file mode 100644 index 0000000..1a17149 --- /dev/null +++ b/packages/flutter_calendar_carousel/test/flutter_calendar_carousel_test.dart @@ -0,0 +1,180 @@ +// This is a basic Flutter widget test. +// To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter +// provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to +// find child widgets in the widget tree, read text, and verify that the values of widget properties +// are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_calendar_carousel/flutter_calendar_carousel.dart'; + +import '../lib/classes/event.dart'; + +Type typeOf<T>() => T; + +void main() { + testWidgets('Default test for Calendar Carousel', + (WidgetTester tester) async { + DateTime? pressedDay; + // Build our app and trigger a frame. + final carousel = CalendarCarousel( + daysHaveCircularBorder: null, + weekendTextStyle: TextStyle( + color: Colors.red, + ), + thisMonthDayBorderColor: Colors.grey, + headerText: 'Custom Header', + weekFormat: true, + height: 200.0, + showIconBehindDayText: true, + customGridViewPhysics: NeverScrollableScrollPhysics(), + markedDateShowIcon: true, + markedDateIconMaxShown: 2, + selectedDayTextStyle: TextStyle( + color: Colors.yellow, + ), + todayTextStyle: TextStyle( + color: Colors.blue, + ), + markedDateIconBuilder: (Event event) { + return event.icon ?? Icon(Icons.help_outline); + }, + todayButtonColor: Colors.transparent, + todayBorderColor: Colors.green, + markedDateMoreShowTotal: true, + // null for not showing hidden events indicator + onDayPressed: (date, event) { + pressedDay = date; + }, + ); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Container( + child: carousel, + ), + ), + )); + + expect(find.byWidget(carousel), findsOneWidget); + expect(pressedDay, isNull); + }); + + testWidgets( + 'make sure onDayPressed is called when the user tap', + (WidgetTester tester) async { + DateTime? pressedDay; + + final carousel = CalendarCarousel( + weekFormat: true, + height: 200.0, + onDayPressed: (date, event) { + pressedDay = date; + }, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Container( + child: carousel, + ), + ), + ), + ); + + expect(find.byWidget(carousel), findsOneWidget); + + expect(pressedDay, isNull); + + await tester.tap( + find.text(DateTime.now().subtract(Duration(days: 1)).day.toString())); + + await tester.pump(); + + expect(pressedDay, isNotNull); + }, + ); + + testWidgets( + 'should do nothing when the user tap and onDayPressed is not provided', + (WidgetTester tester) async { + final carousel = CalendarCarousel( + weekFormat: true, + height: 200.0, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Container( + child: carousel, + ), + ), + ), + ); + + expect(find.byWidget(carousel), findsOneWidget); + + await tester.tap( + find.text(DateTime.now().subtract(Duration(days: 1)).day.toString())); + await tester.pump(); + }, + ); + + testWidgets( + 'make sure onDayLongPressed is called when the user press and hold', + (WidgetTester tester) async { + DateTime? longPressedDay; + + final carousel = CalendarCarousel( + weekFormat: true, + height: 200.0, + onDayLongPressed: (date) { + longPressedDay = date; + }, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Container( + child: carousel, + ), + ), + ), + ); + + expect(find.byWidget(carousel), findsOneWidget); + + expect(longPressedDay, isNull); + + await tester.longPress( + find.text(DateTime.now().subtract(Duration(days: 1)).day.toString())); + await tester.pump(); + + expect(longPressedDay, isNotNull); + }, + ); + + testWidgets( + 'should do nothing when the user press and hold and onDayLongPressed is not provided', + (WidgetTester tester) async { + final carousel = CalendarCarousel( + weekFormat: true, + height: 200.0, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Container( + child: carousel, + ), + ), + ), + ); + + expect(find.byWidget(carousel), findsOneWidget); + + await tester.longPress( + find.text(DateTime.now().subtract(Duration(days: 1)).day.toString())); + await tester.pump(); + }, + ); +} diff --git a/packages/flutter_calendar_carousel/test/src/header_test.dart b/packages/flutter_calendar_carousel/test/src/header_test.dart new file mode 100644 index 0000000..2f9b603 --- /dev/null +++ b/packages/flutter_calendar_carousel/test/src/header_test.dart @@ -0,0 +1,104 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_calendar_carousel/src/calendar_header.dart'; + +import 'package:flutter/material.dart'; + +void main() { + final title = "Test title"; + final margin = const EdgeInsets.symmetric(vertical: 16.0); + final iconColor = Colors.blueAccent; + + testWidgets('Verify Header Defaults', (WidgetTester tester) async { + var headerTapped = false; + var leftPressed = false; + var rightPressed = false; + + await tester.pumpWidget(wrapped(CalendarHeader( + headerTitle: title, + headerMargin: margin, + showHeader: true, + showHeaderButtons: true, + headerIconColor: iconColor, + onHeaderTitlePressed: () => headerTapped = true, + onRightButtonPressed: () => rightPressed = true, + onLeftButtonPressed: () => leftPressed = true, + ))); + + expect(find.text(title), findsOneWidget); + + await tester.tap(find.byType(TextButton)); + + await tester.pump(); + + expect(headerTapped, equals(true)); + + await tester.tap(find.widgetWithIcon(IconButton, Icons.chevron_right)); + + await tester.pump(); + + expect(rightPressed, equals(true)); + + await tester.tap(find.widgetWithIcon(IconButton, Icons.chevron_left)); + + await tester.pump(); + + expect(leftPressed, equals(true)); + }); + + testWidgets('Verify No header Renders', (WidgetTester tester) async { + final noHeaderEmpty = CalendarHeader( + showHeader: false, + headerTitle: '', + onLeftButtonPressed: () {}, + onHeaderTitlePressed: () {}, + onRightButtonPressed: () {}, + ); + + await tester.pumpWidget(Container(child: noHeaderEmpty)); + + expect(find.byWidget(noHeaderEmpty), findsOneWidget); + }); + + testWidgets('Verify Header Is Not Touchable', (WidgetTester tester) async { + await tester.pumpWidget(wrapped(CalendarHeader( + headerTitle: title, + headerMargin: margin, + showHeader: true, + showHeaderButtons: true, + headerIconColor: iconColor, + onHeaderTitlePressed: null, + onRightButtonPressed: () {}, + onLeftButtonPressed: () {}, + ))); + + // the header TextButton Should not render + final touchableHeader = find.byType(TextButton); + + expect(touchableHeader, findsNothing); + }); + + testWidgets('Verify No Header Buttons', (WidgetTester tester) async { + await tester.pumpWidget(wrapped(CalendarHeader( + headerTitle: title, + headerMargin: margin, + showHeader: true, + showHeaderButtons: false, + headerIconColor: iconColor, + onHeaderTitlePressed: () {}, + onRightButtonPressed: () {}, + onLeftButtonPressed: () {}, + ))); + + // the header IconButtons Should not render + final headerButton = find.byType(IconButton); + + expect(headerButton, findsNothing); + }); +} + +// header uses Row which requires MaterialApp as an ancestor +Widget wrapped(Widget widget) => MaterialApp( + home: Container( + child: Material(child: widget), + ), + ); diff --git a/packages/flutter_calendar_carousel/test/src/weekday_row_test.dart b/packages/flutter_calendar_carousel/test/src/weekday_row_test.dart new file mode 100644 index 0000000..95090e4 --- /dev/null +++ b/packages/flutter_calendar_carousel/test/src/weekday_row_test.dart @@ -0,0 +1,128 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_calendar_carousel/flutter_calendar_carousel.dart' + show WeekdayFormat; +import 'package:intl/intl.dart' show DateFormat; + +import 'package:flutter_calendar_carousel/src/weekday_row.dart'; +import 'package:flutter/material.dart'; + +void main() { + final locale = DateFormat.yMMM("en_US"); + final margin = const EdgeInsets.only(bottom: 4.0); + + testWidgets('test short weekday row', (WidgetTester tester) async { + await tester.pumpWidget(wrapped( + WeekdayRow( + 0, + null, + weekdayPadding: EdgeInsets.all(0), + weekdayBackgroundColor: Colors.transparent, + showWeekdays: true, + weekdayFormat: WeekdayFormat.short, + weekdayMargin: margin, + weekdayTextStyle: null, + localeDate: locale, + ), + )); + + expect(find.text('Sun'), findsOneWidget); + expect(find.text('Mon'), findsOneWidget); + expect(find.text('Tue'), findsOneWidget); + expect(find.text('Wed'), findsOneWidget); + expect(find.text('Thu'), findsOneWidget); + expect(find.text('Fri'), findsOneWidget); + expect(find.text('Sat'), findsOneWidget); + }); + + testWidgets('test narrow weekday row', (WidgetTester tester) async { + await tester.pumpWidget(wrapped(WeekdayRow( + 0, + null, + weekdayPadding: EdgeInsets.all(0), + weekdayBackgroundColor: Colors.transparent, + showWeekdays: true, + weekdayFormat: WeekdayFormat.standaloneNarrow, + weekdayMargin: margin, + weekdayTextStyle: null, + localeDate: locale, + ))); + + // sat and sun + expect(find.text('S'), findsNWidgets(2)); + // thurs and tues + expect(find.text('T'), findsNWidgets(2)); + + expect(find.text('M'), findsOneWidget); + expect(find.text('W'), findsOneWidget); + expect(find.text('F'), findsOneWidget); + }); + + testWidgets('test standalone weekday row', (WidgetTester tester) async { + await tester.pumpWidget(wrapped(WeekdayRow( + 0, + null, + weekdayPadding: EdgeInsets.all(0), + weekdayBackgroundColor: Colors.transparent, + showWeekdays: true, + weekdayFormat: WeekdayFormat.standalone, + weekdayMargin: margin, + weekdayTextStyle: null, + localeDate: locale, + ))); + + expect(find.text('Sunday'), findsOneWidget); + expect(find.text('Monday'), findsOneWidget); + expect(find.text('Tuesday'), findsOneWidget); + expect(find.text('Wednesday'), findsOneWidget); + expect(find.text('Thursday'), findsOneWidget); + expect(find.text('Friday'), findsOneWidget); + expect(find.text('Saturday'), findsOneWidget); + }); + + testWidgets('test standalone short weekday row', (WidgetTester tester) async { + await tester.pumpWidget(wrapped(WeekdayRow( + 0, + null, + weekdayPadding: EdgeInsets.all(0), + weekdayBackgroundColor: Colors.transparent, + showWeekdays: true, + weekdayFormat: WeekdayFormat.standaloneShort, + weekdayMargin: margin, + weekdayTextStyle: null, + localeDate: locale, + ))); + + expect(find.text('Sun'), findsOneWidget); + expect(find.text('Mon'), findsOneWidget); + expect(find.text('Tue'), findsOneWidget); + expect(find.text('Wed'), findsOneWidget); + expect(find.text('Thu'), findsOneWidget); + expect(find.text('Fri'), findsOneWidget); + expect(find.text('Sat'), findsOneWidget); + }); + + testWidgets('test row does not render', (WidgetTester tester) async { + final emptyContainer = WeekdayRow( + 0, + null, + weekdayPadding: EdgeInsets.all(0), + weekdayBackgroundColor: Colors.transparent, + showWeekdays: false, + weekdayFormat: WeekdayFormat.standaloneNarrow, + weekdayMargin: margin, + weekdayTextStyle: null, + localeDate: locale, + ); + + await tester.pumpWidget(emptyContainer); + + expect(find.byType(Container), findsOneWidget); + + expect(find.byType(Row), findsNothing); + }); +} + +Widget wrapped(Widget widget) => Directionality( + textDirection: TextDirection.ltr, + child: widget, + ); |