├── example ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ └── Runner.xcodeproj │ │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── build.gradle ├── .metadata ├── pubspec.yaml ├── README.md ├── .gitignore ├── pubspec.lock └── lib │ ├── components │ └── components.dart │ └── main.dart ├── images └── demo.gif ├── .metadata ├── pubspec.yaml ├── .travis.yml ├── lib ├── date_helper.dart ├── date_widget.dart └── horizontal_calendar.dart ├── LICENSE ├── test ├── date_helper_test.dart ├── date_widget_test.dart └── horizontal_calendar_test.dart ├── CHANGELOG.md ├── .gitignore ├── README.md └── pubspec.lock /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.enableR8=true 4 | -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/images/demo.gif -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutelabs/horizontal_calendar/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 68587a0916366e9512a78df22c44163d041dd5f3 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 68587a0916366e9512a78df22c44163d041dd5f3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.2.2 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | cupertino_icons: ^0.1.2 13 | horizontal_calendar_widget: 14 | path: ../ 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | flutter: 21 | uses-material-design: true -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: horizontal_calendar_widget 2 | description: Easy to use, highly customizable horizontal calendar. Single or up to x days selection, with Internationalization support. 3 | version: 1.0.2 4 | homepage: https://github.com/solutelabs/horizontal_calendar 5 | 6 | environment: 7 | sdk: ">=2.2.2 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | flutter_localizations: 13 | sdk: flutter 14 | intl: ^0.16.1 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | os: 3 | - osx 4 | env: 5 | - FLUTTER_CHANNEL="stable" 6 | sudo: false 7 | before_script: 8 | - cd .. 9 | - git clone https://github.com/flutter/flutter.git -b $FLUTTER_CHANNEL 10 | - export PATH=$PATH:$PWD/flutter/bin:$PWD/flutter/bin/cache/dart-sdk/bin 11 | - cd - 12 | - flutter doctor 13 | script: 14 | - set -e # abort CI if an error happens 15 | - flutter test --coverage 16 | 17 | # export coverage 18 | - bash <(curl -s https://codecov.io/bash); 19 | matrix: 20 | fast_finish: true 21 | cache: 22 | directories: 23 | - $HOME/.pub-cache -------------------------------------------------------------------------------- /lib/date_helper.dart: -------------------------------------------------------------------------------- 1 | List getDateList(DateTime firstDate, DateTime lastDate) { 2 | List list = List(); 3 | int count = daysCount(toDateMonthYear(firstDate), toDateMonthYear(lastDate)); 4 | for (int i = 0; i < count; i++) { 5 | list.add(toDateMonthYear(firstDate).add(Duration(days: i))); 6 | } 7 | return list; 8 | } 9 | 10 | DateTime toDateMonthYear(DateTime dateTime) { 11 | return DateTime(dateTime.year, dateTime.month, dateTime.day); 12 | } 13 | 14 | int daysCount(DateTime first, DateTime last) => 15 | last.difference(first).inDays + 1; 16 | 17 | enum LabelType { 18 | date, 19 | month, 20 | weekday, 21 | } 22 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # horizontal_calendar 2 | 3 | An Example Application that demonstrate all features provided by horizontal_calendar 4 | 5 | ## Getting Started 6 | 7 | To watch demo, clone repo and execute 8 | ``` 9 | cd example/ 10 | flutter run 11 | ``` 12 | 13 | To use in your application: 14 | 15 | ```dart 16 | class HorizontalCalendarDemo extends StatelessWidget { 17 | @override 18 | Widget build(BuildContext context) { 19 | return HorizontalCalendar( 20 | firstDate: DateTime.now(), 21 | lastDate: DateTime.now().add( 22 | Duration(days: 15), 23 | ), 24 | //pass other properties as required 25 | ); 26 | } 27 | } 28 | ``` -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.2.71' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 SoluteLabs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /test/date_helper_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:horizontal_calendar_widget/date_helper.dart'; 3 | 4 | void main() { 5 | test( 6 | 'Returned date should have only year,month,date and other components as zero', 7 | () { 8 | final actualDate = DateTime.now(); 9 | final date = toDateMonthYear(actualDate); 10 | expect(date.year, actualDate.year); 11 | expect(date.month, actualDate.month); 12 | expect(date.day, actualDate.day); 13 | expect(date.hour, 0); 14 | expect(date.minute, 0); 15 | }); 16 | 17 | test('Return date lists of in between days', () { 18 | final expected = [ 19 | _toDateMonthYear(DateTime.now()), 20 | _toDateMonthYear(DateTime.now().add(Duration(days: 1))), 21 | ]; 22 | final list = getDateList(DateTime.now(), DateTime.now().add(Duration(days: 1))); 23 | expect(list, expected); 24 | }); 25 | 26 | test('Return single day, if from and to date are same', () { 27 | final expected = [_toDateMonthYear(DateTime.now())]; 28 | final list = getDateList(DateTime.now(), DateTime.now()); 29 | expect(list, expected); 30 | }); 31 | } 32 | 33 | DateTime _toDateMonthYear(DateTime dateTime) { 34 | return DateTime(dateTime.year, dateTime.month, dateTime.day); 35 | } 36 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.2] 2 | - Added Min Selected date count (Author [dariof28](https://github.com/dariof28)) 3 | - Added Ability to show month and dayOfWeek labels in uppercase (Author [dariof28](https://github.com/dariof28)) 4 | - Upgraded Flutter version to v1.17.2 5 | 6 | ## [1.0.1] 7 | - Changed intl from main to transitive dependency to avoid version conflicts. 8 | 9 | ## [1.0.0] Initial Release 10 | 11 | - [x] Custom date range (First & Last Date) 12 | - [x] Single or up to x days selection 13 | - [x] `onDateSelected`, `onDateUnSelected`, `onDateLongTap`, `onMaxDateSelectionReached` events. 14 | - [x] Support custom ScrollController 15 | - [x] Initial selected dates 16 | - [x] Granular control to disable dates. 17 | - [x] Internationalization support 18 | - [x] Month / Date / Week Day label order customization 19 | - [x] Month / Week day label hide / show 20 | - [x] Custom [TextStyles](https://api.flutter.dev/flutter/painting/TextStyle-class.html) for Month, Date, WeekDay 21 | - [x] Custom [TextStyles](https://api.flutter.dev/flutter/painting/TextStyle-class.html) for selected Month, selected Date, selected WeekDay 22 | - [x] Customizable month format (e.g. `MM`,`MMM`) 23 | - [x] Customizable date format (e.g. `dd`,`d`) 24 | - [x] Customizable week day format (e.g. `EE`,`EEE`) 25 | - [x] Default date cell [Decoration](https://api.flutter.dev/flutter/painting/Decoration-class.html) 26 | - [x] Selected date cell [Decoration](https://api.flutter.dev/flutter/painting/Decoration-class.html) 27 | - [x] Disabled date cell [Decoration](https://api.flutter.dev/flutter/painting/Decoration-class.html) 28 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | /coverage/ 75 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | 75 | # custom 76 | # 77 | *.xcodeproj/* 78 | !*.xcodeproj/project.pbxproj 79 | !*.xcodeproj/xcshareddata 80 | *.xcworkspace 81 | *.generated.swift 82 | .DS_Store 83 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 66 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 67 | } 68 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/date_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:horizontal_calendar_widget/date_helper.dart'; 3 | import 'package:intl/intl.dart'; 4 | 5 | class DateWidget extends StatelessWidget { 6 | final defaultDateFormat = 'dd'; 7 | final defaultMonthFormat = 'MMM'; 8 | final defaultWeekDayFormat = 'EEE'; 9 | 10 | final DateTime date; 11 | final TextStyle monthTextStyle; 12 | final TextStyle selectedMonthTextStyle; 13 | final String monthFormat; 14 | final TextStyle dateTextStyle; 15 | final TextStyle selectedDateTextStyle; 16 | final String dateFormat; 17 | final TextStyle weekDayTextStyle; 18 | final TextStyle selectedWeekDayTextStyle; 19 | final String weekDayFormat; 20 | final VoidCallback onTap; 21 | final VoidCallback onLongTap; 22 | final Decoration defaultDecoration; 23 | final Decoration selectedDecoration; 24 | final Decoration disabledDecoration; 25 | final bool isSelected; 26 | final bool isDisabled; 27 | final EdgeInsetsGeometry padding; 28 | final List labelOrder; 29 | final bool isLabelUppercase; 30 | 31 | const DateWidget({ 32 | Key key, 33 | @required this.date, 34 | this.onTap, 35 | this.onLongTap, 36 | this.isSelected = false, 37 | this.isDisabled = false, 38 | this.monthTextStyle, 39 | this.selectedMonthTextStyle, 40 | this.monthFormat, 41 | this.dateTextStyle, 42 | this.selectedDateTextStyle, 43 | this.dateFormat, 44 | this.weekDayTextStyle, 45 | this.selectedWeekDayTextStyle, 46 | this.weekDayFormat, 47 | this.defaultDecoration, 48 | this.selectedDecoration = const BoxDecoration(color: Colors.cyan), 49 | this.disabledDecoration = const BoxDecoration(color: Colors.grey), 50 | this.padding, 51 | this.labelOrder, 52 | this.isLabelUppercase = false, 53 | }) : super(key: key); 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | final titleStyle = Theme.of(context).textTheme.headline6; 58 | final subTitleStyle = Theme.of(context).textTheme.subtitle2; 59 | 60 | final monthStyle = isSelected 61 | ? selectedMonthTextStyle ?? monthTextStyle ?? subTitleStyle 62 | : monthTextStyle ?? subTitleStyle; 63 | final dateStyle = isSelected 64 | ? selectedDateTextStyle ?? dateTextStyle ?? titleStyle 65 | : dateTextStyle ?? titleStyle; 66 | final dayStyle = isSelected 67 | ? selectedWeekDayTextStyle ?? weekDayTextStyle ?? subTitleStyle 68 | : weekDayTextStyle ?? subTitleStyle; 69 | 70 | return GestureDetector( 71 | onTap: isDisabled ? null : onTap, 72 | onLongPress: isDisabled ? null : onLongTap, 73 | child: Container( 74 | decoration: isSelected 75 | ? selectedDecoration 76 | : isDisabled ? disabledDecoration : defaultDecoration, 77 | child: Padding( 78 | padding: padding, 79 | child: Column( 80 | mainAxisAlignment: MainAxisAlignment.center, 81 | children: [ 82 | ...labelOrder.map((type) { 83 | Text text; 84 | switch (type) { 85 | case LabelType.month: 86 | text = Text( 87 | isLabelUppercase 88 | ? _monthLabel().toUpperCase() 89 | : _monthLabel(), 90 | style: monthStyle, 91 | ); 92 | break; 93 | case LabelType.date: 94 | text = Text( 95 | DateFormat(dateFormat ?? defaultDateFormat).format(date), 96 | style: dateStyle, 97 | ); 98 | break; 99 | case LabelType.weekday: 100 | text = Text( 101 | isLabelUppercase 102 | ? _weekDayLabel().toUpperCase() 103 | : _weekDayLabel(), 104 | style: dayStyle, 105 | ); 106 | break; 107 | } 108 | return text; 109 | }).toList(growable: false) 110 | ], 111 | ), 112 | ), 113 | ), 114 | ); 115 | } 116 | 117 | String _monthLabel() { 118 | return DateFormat(monthFormat ?? defaultMonthFormat).format(date); 119 | } 120 | 121 | String _weekDayLabel() { 122 | return DateFormat(weekDayFormat ?? defaultWeekDayFormat).format(date); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # horizontal_calendar 2 | [![Build Status](https://travis-ci.org/solutelabs/horizontal_calendar.svg?branch=master)](https://travis-ci.org/solutelabs/horizontal_calendar) [![codecov](https://codecov.io/gh/solutelabs/horizontal_calendar/branch/master/graph/badge.svg)](https://codecov.io/gh/solutelabs/horizontal_calendar) [![SoluteLabs](https://img.shields.io/badge/madeby-solutelabs-blue)](https://www.solutelabs.com/) [![Twitter Follow](https://img.shields.io/twitter/follow/solutelabs?label=Follow%20SoluteLabs&style=social)](https://twitter.com/solutelabs) 3 | 4 | Easy to use, highly customizable horizontal calendar. 5 | 6 | ## Features 7 | 8 | - [x] Custom date range (First & Last Date) 9 | - [x] Single or up to x days selection 10 | - [x] `onDateSelected`, `onDateUnSelected`, `onDateLongTap`, `onMaxDateSelectionReached` events. 11 | - [x] Support custom ScrollController 12 | - [x] Initial selected dates 13 | - [x] Granular control to disable dates. 14 | - [x] Internationalization support 15 | - [x] Month / Date / Week Day label order customization 16 | - [x] Month / Week day label hide / show 17 | - [x] Custom [TextStyles](https://api.flutter.dev/flutter/painting/TextStyle-class.html) for Month, Date, WeekDay 18 | - [x] Custom [TextStyles](https://api.flutter.dev/flutter/painting/TextStyle-class.html) for selected Month, selected Date, selected WeekDay 19 | - [x] Customizable month format (e.g. `MM`,`MMM`) 20 | - [x] Customizable date format (e.g. `dd`,`d`) 21 | - [x] Customizable week day format (e.g. `EE`,`EEE`) 22 | - [x] Default date cell [Decoration](https://api.flutter.dev/flutter/painting/Decoration-class.html) 23 | - [x] Selected date cell [Decoration](https://api.flutter.dev/flutter/painting/Decoration-class.html) 24 | - [x] Disabled date cell [Decoration](https://api.flutter.dev/flutter/painting/Decoration-class.html) 25 | 26 |

27 | 28 |

29 | 30 | ## Properties 31 | 32 | | Property Name | Property Type | Description | Default value | 33 | | ------------------------- | ---------------------------------- | ------------ | ---------------------- | 34 | | height | double |Height of widget | 100 | 35 | | firstDate | DateTime |First Date of calendar | - | 36 | | lastDate | DateTime |Last Date of calendar | - | 37 | | minSelectedDateCount | int | Count of min selectable dates | 0 | 38 | | maxSelectedDateCount | int | Count of max selectable dates | 1 | 39 | | onDateSelected | Function(DateTime dateTime) |Callback when date is selected | - | 40 | | onDateLongTap | Function(DateTime dateTime) |Callback when date cell is long pressed | - | 41 | | onDateUnSelected | Function(DateTime dateTime) |Callback when date is unselected | - | 42 | | onMaxDateSelectionReached | VoidCallback |Callback when max date selection count is reached | - | 43 | | initialSelectedDates | List<DateTime> | List of initially selected dates | Empty List | 44 | | isDateDisabled | bool Function(DateTime dateTime) | Function that returns bool to check if particular date is disabled | - | 45 | | labelOrder | List<LabelType> | Order of labels | [ LabelType.month, LabelType.date, LabelType.weekday] | 46 | | scrollController | ScrollController | Scroll Controller of horizontal list | - | 47 | | monthTextStyle | TextStyle | Month label TextStyle | titleTheme | 48 | | selectedMonthTextStyle | TextStyle |Selected Month label TextStyle | monthTextStyle | 49 | | monthFormat | String | Format of month | `MMM` | 50 | | dateTextStyle | TextStyle | Date label TextStyle | subTitleTheme | 51 | | selectedDateTextStyle | TextStyle | Selected Date label TextStyle | dateTextStyle | 52 | | dateFormat | String | Format of date | `dd` | 53 | | weekDayTextStyle | TextStyle | Week day label TextStyle | subTitleTheme | 54 | | selectedWeekDayTextStyle | TextStyle | Selected Week day label TextStyle | dateTextStyle | 55 | | weekDayFormat | String | Format of week day | `EEE` | 56 | | defaultDecoration | Decoration | Default Decoration to be applied to date cell | - | 57 | | selectedDecoration | Decoration |Decoration to be applied to selected date cell | - | 58 | | disabledDecoration | Decoration |Decoration to be applied to disabled date cell | - | 59 | | spacingBetweenDates | double | Spacing between two cells of date | 8.0 | 60 | | padding | EdgeInsetsGeometry | Padding to date cell | `EdgeInsets.all(8.0)` | 61 | 62 | ## State Management in horizontal_calendar 63 | 64 | `initialSelectedDates` will only be taken when the widget built for the first time. `horizontal_calendar` will manage the Subsequent dates selection and un selection. 65 | 66 | To get the initial control over the host app, one can pass the UniqueKey. 67 | 68 | e.g. 69 | ```dart 70 | HorizontalCalendar( 71 | key: UniqueKey(), 72 | ); 73 | ``` 74 | 75 | ## Issues and Feedback 76 | 77 | * For any issue and feedback please [create issue](https://github.com/solutelabs/horizontal_calendar/issues/new) on Github repo. -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.1" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.0.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.3" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.12" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.4" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_localizations: 66 | dependency: "direct main" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | flutter_test: 71 | dependency: "direct dev" 72 | description: flutter 73 | source: sdk 74 | version: "0.0.0" 75 | image: 76 | dependency: transitive 77 | description: 78 | name: image 79 | url: "https://pub.dartlang.org" 80 | source: hosted 81 | version: "2.1.12" 82 | intl: 83 | dependency: "direct main" 84 | description: 85 | name: intl 86 | url: "https://pub.dartlang.org" 87 | source: hosted 88 | version: "0.16.1" 89 | matcher: 90 | dependency: transitive 91 | description: 92 | name: matcher 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "0.12.6" 96 | meta: 97 | dependency: transitive 98 | description: 99 | name: meta 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.1.8" 103 | path: 104 | dependency: transitive 105 | description: 106 | name: path 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.6.4" 110 | petitparser: 111 | dependency: transitive 112 | description: 113 | name: petitparser 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "2.4.0" 117 | quiver: 118 | dependency: transitive 119 | description: 120 | name: quiver 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "2.1.3" 124 | sky_engine: 125 | dependency: transitive 126 | description: flutter 127 | source: sdk 128 | version: "0.0.99" 129 | source_span: 130 | dependency: transitive 131 | description: 132 | name: source_span 133 | url: "https://pub.dartlang.org" 134 | source: hosted 135 | version: "1.7.0" 136 | stack_trace: 137 | dependency: transitive 138 | description: 139 | name: stack_trace 140 | url: "https://pub.dartlang.org" 141 | source: hosted 142 | version: "1.9.3" 143 | stream_channel: 144 | dependency: transitive 145 | description: 146 | name: stream_channel 147 | url: "https://pub.dartlang.org" 148 | source: hosted 149 | version: "2.0.0" 150 | string_scanner: 151 | dependency: transitive 152 | description: 153 | name: string_scanner 154 | url: "https://pub.dartlang.org" 155 | source: hosted 156 | version: "1.0.5" 157 | term_glyph: 158 | dependency: transitive 159 | description: 160 | name: term_glyph 161 | url: "https://pub.dartlang.org" 162 | source: hosted 163 | version: "1.1.0" 164 | test_api: 165 | dependency: transitive 166 | description: 167 | name: test_api 168 | url: "https://pub.dartlang.org" 169 | source: hosted 170 | version: "0.2.15" 171 | typed_data: 172 | dependency: transitive 173 | description: 174 | name: typed_data 175 | url: "https://pub.dartlang.org" 176 | source: hosted 177 | version: "1.1.6" 178 | vector_math: 179 | dependency: transitive 180 | description: 181 | name: vector_math 182 | url: "https://pub.dartlang.org" 183 | source: hosted 184 | version: "2.0.8" 185 | xml: 186 | dependency: transitive 187 | description: 188 | name: xml 189 | url: "https://pub.dartlang.org" 190 | source: hosted 191 | version: "3.6.1" 192 | sdks: 193 | dart: ">=2.6.0 <3.0.0" 194 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.1" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.0.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.3" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.12" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.4" 60 | cupertino_icons: 61 | dependency: "direct main" 62 | description: 63 | name: cupertino_icons 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.1.3" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_localizations: 73 | dependency: transitive 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | flutter_test: 78 | dependency: "direct dev" 79 | description: flutter 80 | source: sdk 81 | version: "0.0.0" 82 | horizontal_calendar_widget: 83 | dependency: "direct main" 84 | description: 85 | path: ".." 86 | relative: true 87 | source: path 88 | version: "1.0.1" 89 | image: 90 | dependency: transitive 91 | description: 92 | name: image 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "2.1.12" 96 | intl: 97 | dependency: transitive 98 | description: 99 | name: intl 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "0.16.1" 103 | matcher: 104 | dependency: transitive 105 | description: 106 | name: matcher 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "0.12.6" 110 | meta: 111 | dependency: transitive 112 | description: 113 | name: meta 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.1.8" 117 | path: 118 | dependency: transitive 119 | description: 120 | name: path 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.6.4" 124 | petitparser: 125 | dependency: transitive 126 | description: 127 | name: petitparser 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "2.4.0" 131 | quiver: 132 | dependency: transitive 133 | description: 134 | name: quiver 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "2.1.3" 138 | sky_engine: 139 | dependency: transitive 140 | description: flutter 141 | source: sdk 142 | version: "0.0.99" 143 | source_span: 144 | dependency: transitive 145 | description: 146 | name: source_span 147 | url: "https://pub.dartlang.org" 148 | source: hosted 149 | version: "1.7.0" 150 | stack_trace: 151 | dependency: transitive 152 | description: 153 | name: stack_trace 154 | url: "https://pub.dartlang.org" 155 | source: hosted 156 | version: "1.9.3" 157 | stream_channel: 158 | dependency: transitive 159 | description: 160 | name: stream_channel 161 | url: "https://pub.dartlang.org" 162 | source: hosted 163 | version: "2.0.0" 164 | string_scanner: 165 | dependency: transitive 166 | description: 167 | name: string_scanner 168 | url: "https://pub.dartlang.org" 169 | source: hosted 170 | version: "1.0.5" 171 | term_glyph: 172 | dependency: transitive 173 | description: 174 | name: term_glyph 175 | url: "https://pub.dartlang.org" 176 | source: hosted 177 | version: "1.1.0" 178 | test_api: 179 | dependency: transitive 180 | description: 181 | name: test_api 182 | url: "https://pub.dartlang.org" 183 | source: hosted 184 | version: "0.2.15" 185 | typed_data: 186 | dependency: transitive 187 | description: 188 | name: typed_data 189 | url: "https://pub.dartlang.org" 190 | source: hosted 191 | version: "1.1.6" 192 | vector_math: 193 | dependency: transitive 194 | description: 195 | name: vector_math 196 | url: "https://pub.dartlang.org" 197 | source: hosted 198 | version: "2.0.8" 199 | xml: 200 | dependency: transitive 201 | description: 202 | name: xml 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "3.6.1" 206 | sdks: 207 | dart: ">=2.6.0 <3.0.0" 208 | -------------------------------------------------------------------------------- /lib/horizontal_calendar.dart: -------------------------------------------------------------------------------- 1 | library horizontal_calendar; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:horizontal_calendar_widget/date_helper.dart'; 5 | import 'package:horizontal_calendar_widget/date_widget.dart'; 6 | 7 | typedef DateBuilder = bool Function(DateTime dateTime); 8 | 9 | typedef DateSelectionCallBack = void Function(DateTime dateTime); 10 | 11 | class HorizontalCalendar extends StatefulWidget { 12 | final DateTime firstDate; 13 | final DateTime lastDate; 14 | final double height; 15 | final TextStyle monthTextStyle; 16 | final TextStyle selectedMonthTextStyle; 17 | final String monthFormat; 18 | final TextStyle dateTextStyle; 19 | final TextStyle selectedDateTextStyle; 20 | final String dateFormat; 21 | final TextStyle weekDayTextStyle; 22 | final TextStyle selectedWeekDayTextStyle; 23 | final String weekDayFormat; 24 | final DateSelectionCallBack onDateSelected; 25 | final DateSelectionCallBack onDateLongTap; 26 | final DateSelectionCallBack onDateUnSelected; 27 | final VoidCallback onMaxDateSelectionReached; 28 | final Decoration defaultDecoration; 29 | final Decoration selectedDecoration; 30 | final Decoration disabledDecoration; 31 | final DateBuilder isDateDisabled; 32 | final List initialSelectedDates; 33 | final ScrollController scrollController; 34 | final double spacingBetweenDates; 35 | final EdgeInsetsGeometry padding; 36 | final List labelOrder; 37 | final int minSelectedDateCount; 38 | final int maxSelectedDateCount; 39 | final bool isLabelUppercase; 40 | 41 | HorizontalCalendar({ 42 | Key key, 43 | this.height = 100, 44 | @required this.firstDate, 45 | @required this.lastDate, 46 | this.scrollController, 47 | this.onDateSelected, 48 | this.onDateLongTap, 49 | this.onDateUnSelected, 50 | this.onMaxDateSelectionReached, 51 | this.minSelectedDateCount = 0, 52 | this.maxSelectedDateCount = 1, 53 | this.monthTextStyle, 54 | this.selectedMonthTextStyle, 55 | this.monthFormat, 56 | this.dateTextStyle, 57 | this.selectedDateTextStyle, 58 | this.dateFormat, 59 | this.weekDayTextStyle, 60 | this.selectedWeekDayTextStyle, 61 | this.weekDayFormat, 62 | this.defaultDecoration, 63 | this.selectedDecoration, 64 | this.disabledDecoration, 65 | this.isDateDisabled, 66 | this.initialSelectedDates = const [], 67 | this.spacingBetweenDates = 8.0, 68 | this.padding = const EdgeInsets.all(8.0), 69 | this.labelOrder = const [ 70 | LabelType.month, 71 | LabelType.date, 72 | LabelType.weekday, 73 | ], 74 | this.isLabelUppercase = false, 75 | }) : assert(firstDate != null), 76 | assert(lastDate != null), 77 | assert( 78 | toDateMonthYear(lastDate) == toDateMonthYear(firstDate) || 79 | toDateMonthYear(lastDate).isAfter(toDateMonthYear(firstDate)), 80 | ), 81 | assert(labelOrder != null && labelOrder.isNotEmpty, 82 | 'Label Order should not be empty'), 83 | assert(minSelectedDateCount <= maxSelectedDateCount), 84 | assert(minSelectedDateCount <= initialSelectedDates.length, 85 | "You must provide at least $minSelectedDateCount initialSelectedDates"), 86 | assert(maxSelectedDateCount >= initialSelectedDates.length, 87 | "You can't provide more than $maxSelectedDateCount initialSelectedDates"), 88 | super(key: key); 89 | 90 | @override 91 | _HorizontalCalendarState createState() => _HorizontalCalendarState(); 92 | } 93 | 94 | class _HorizontalCalendarState extends State { 95 | final List allDates = []; 96 | final List selectedDates = []; 97 | 98 | @override 99 | void initState() { 100 | super.initState(); 101 | allDates.addAll(getDateList(widget.firstDate, widget.lastDate)); 102 | selectedDates.addAll(widget.initialSelectedDates.map((toDateMonthYear))); 103 | } 104 | 105 | @override 106 | Widget build(BuildContext context) { 107 | return Container( 108 | height: widget.height, 109 | child: Center( 110 | child: ListView.builder( 111 | controller: widget.scrollController ?? ScrollController(), 112 | scrollDirection: Axis.horizontal, 113 | itemCount: allDates.length, 114 | itemBuilder: (context, index) { 115 | final date = allDates[index]; 116 | return Row( 117 | children: [ 118 | DateWidget( 119 | key: Key(date.toIso8601String()), 120 | padding: widget.padding, 121 | isSelected: selectedDates.contains(date), 122 | isDisabled: widget.isDateDisabled != null 123 | ? widget.isDateDisabled(date) 124 | : false, 125 | date: date, 126 | monthTextStyle: widget.monthTextStyle, 127 | selectedMonthTextStyle: widget.selectedMonthTextStyle, 128 | monthFormat: widget.monthFormat, 129 | dateTextStyle: widget.dateTextStyle, 130 | selectedDateTextStyle: widget.selectedDateTextStyle, 131 | dateFormat: widget.dateFormat, 132 | weekDayTextStyle: widget.weekDayTextStyle, 133 | selectedWeekDayTextStyle: widget.selectedWeekDayTextStyle, 134 | weekDayFormat: widget.weekDayFormat, 135 | defaultDecoration: widget.defaultDecoration, 136 | selectedDecoration: widget.selectedDecoration, 137 | disabledDecoration: widget.disabledDecoration, 138 | labelOrder: widget.labelOrder, 139 | isLabelUppercase: widget.isLabelUppercase ?? false, 140 | onTap: () { 141 | if (!selectedDates.contains(date)) { 142 | if (widget.maxSelectedDateCount == 1 && 143 | selectedDates.length == 1) { 144 | selectedDates.clear(); 145 | } else if (widget.maxSelectedDateCount == 146 | selectedDates.length) { 147 | if (widget.onMaxDateSelectionReached != null) { 148 | widget.onMaxDateSelectionReached(); 149 | } 150 | return; 151 | } 152 | 153 | selectedDates.add(date); 154 | if (widget.onDateSelected != null) { 155 | widget.onDateSelected(date); 156 | } 157 | } else if (selectedDates.length > 158 | widget.minSelectedDateCount) { 159 | final isRemoved = selectedDates.remove(date); 160 | if (isRemoved && widget.onDateUnSelected != null) { 161 | widget.onDateUnSelected(date); 162 | } 163 | } 164 | setState(() {}); 165 | }, 166 | onLongTap: () => widget.onDateLongTap != null 167 | ? widget.onDateLongTap(date) 168 | : null, 169 | ), 170 | SizedBox(width: widget.spacingBetweenDates), 171 | ], 172 | ); 173 | }, 174 | ), 175 | ), 176 | ); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /example/lib/components/components.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PropertyLabel extends StatelessWidget { 4 | final String label; 5 | final Widget value; 6 | final VoidCallback onTap; 7 | 8 | const PropertyLabel({ 9 | Key key, 10 | this.label, 11 | this.value, 12 | this.onTap, 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListTile( 18 | title: Text(label), 19 | subtitle: value, 20 | onTap: onTap, 21 | ); 22 | } 23 | } 24 | 25 | class DropDownProperty extends StatelessWidget { 26 | final String hint; 27 | final T value; 28 | final List options; 29 | final Function(T selectedValue) onChange; 30 | 31 | const DropDownProperty({ 32 | Key key, 33 | @required this.hint, 34 | @required this.value, 35 | @required this.options, 36 | @required this.onChange, 37 | }) : super(key: key); 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return DropdownButtonHideUnderline( 42 | child: DropdownButton( 43 | hint: Text(hint), 44 | value: value, 45 | items: options.map((T value) { 46 | return DropdownMenuItem( 47 | value: value, 48 | child: Text(value.toString()), 49 | ); 50 | }).toList(), 51 | onChanged: (format) { 52 | if (format != null) { 53 | onChange(format); 54 | } 55 | }, 56 | ), 57 | ); 58 | } 59 | } 60 | 61 | class ColorDropDown extends StatelessWidget { 62 | final String hint; 63 | final Color value; 64 | final List options; 65 | final Function(Color selectedValue) onChange; 66 | 67 | const ColorDropDown({ 68 | Key key, 69 | @required this.hint, 70 | @required this.value, 71 | this.options = const [ 72 | Colors.transparent, 73 | Colors.blue, 74 | Colors.purple, 75 | Colors.yellow, 76 | Colors.green, 77 | Colors.grey, 78 | ], 79 | @required this.onChange, 80 | }) : super(key: key); 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | return DropdownButtonHideUnderline( 85 | child: DropdownButton( 86 | hint: Text(hint), 87 | value: value, 88 | items: options.map((Color value) { 89 | return DropdownMenuItem( 90 | value: value, 91 | child: Padding( 92 | padding: const EdgeInsets.all(8.0), 93 | child: Container( 94 | height: 50, 95 | width: 50, 96 | color: value, 97 | ), 98 | ), 99 | ); 100 | }).toList(), 101 | onChanged: (format) { 102 | if (format != null) { 103 | onChange(format); 104 | } 105 | }, 106 | ), 107 | ); 108 | } 109 | } 110 | 111 | class Header extends StatelessWidget { 112 | final String headerText; 113 | 114 | const Header({Key key, this.headerText}) : super(key: key); 115 | 116 | @override 117 | Widget build(BuildContext context) { 118 | return Column( 119 | children: [ 120 | Padding( 121 | padding: const EdgeInsets.symmetric( 122 | horizontal: 8, 123 | vertical: 16, 124 | ), 125 | child: Row( 126 | children: [ 127 | Text( 128 | headerText, 129 | style: Theme.of(context).textTheme.headline6.copyWith( 130 | color: Theme.of(context).primaryColor, 131 | ), 132 | ), 133 | SizedBox(width: 8), 134 | Expanded( 135 | child: Container( 136 | height: 2, 137 | color: Theme.of(context).primaryColor, 138 | ), 139 | ), 140 | ], 141 | ), 142 | ), 143 | ], 144 | ); 145 | } 146 | } 147 | 148 | class DecorationBuilder extends StatelessWidget { 149 | final BoxShape decorationShape; 150 | final Function(BoxShape) onSelectShape; 151 | final bool isCircularRadius; 152 | final Function(bool) onCircularRadiusChange; 153 | final Color color; 154 | final Function(Color) onColorChange; 155 | 156 | const DecorationBuilder({ 157 | Key key, 158 | @required this.decorationShape, 159 | @required this.onSelectShape, 160 | @required this.isCircularRadius, 161 | @required this.onCircularRadiusChange, 162 | @required this.color, 163 | @required this.onColorChange, 164 | }) : super(key: key); 165 | 166 | @override 167 | Widget build(BuildContext context) { 168 | return Column( 169 | crossAxisAlignment: CrossAxisAlignment.stretch, 170 | children: [ 171 | PropertyLabel( 172 | label: 'Decoration Shape', 173 | value: DropDownProperty( 174 | hint: 'Select Decoration Type', 175 | value: decorationShape, 176 | options: BoxShape.values, 177 | onChange: onSelectShape, 178 | ), 179 | ), 180 | if (decorationShape == BoxShape.rectangle) 181 | PropertyLabel( 182 | label: 'Is Circular Border?', 183 | value: Checkbox( 184 | value: isCircularRadius, 185 | onChanged: onCircularRadiusChange, 186 | ), 187 | ), 188 | PropertyLabel( 189 | label: 'Decoration Color', 190 | value: ColorDropDown( 191 | hint: 'Select Decoration Color', 192 | value: color, 193 | onChange: onColorChange, 194 | ), 195 | ), 196 | ], 197 | ); 198 | } 199 | } 200 | 201 | typedef RangeSelectionCallback = void Function(RangeValues newRange); 202 | 203 | class CustomRangeSlider extends StatefulWidget { 204 | final RangeValues range; 205 | final double min; 206 | final double max; 207 | final RangeSelectionCallback onRangeSet; 208 | 209 | CustomRangeSlider({ 210 | @required this.range, 211 | @required this.onRangeSet, 212 | this.min, 213 | this.max, 214 | }) : assert(range != null); 215 | 216 | @override 217 | _CustomRangeSliderState createState() => _CustomRangeSliderState(); 218 | } 219 | 220 | class _CustomRangeSliderState extends State { 221 | RangeValues range; 222 | 223 | @override 224 | void initState() { 225 | super.initState(); 226 | range = widget.range; 227 | } 228 | 229 | @override 230 | Widget build(BuildContext context) { 231 | return Row( 232 | children: [ 233 | Text( 234 | "${range.start.toInt()}", 235 | style: Theme.of(context).textTheme.headline6, 236 | ), 237 | Expanded( 238 | child: RangeSlider( 239 | values: range, 240 | min: widget.min, 241 | max: widget.max, 242 | divisions: widget.max.toInt(), 243 | onChanged: (newRange) => { 244 | setState(() { 245 | range = newRange; 246 | if (widget.onRangeSet != null) { 247 | widget.onRangeSet(newRange); 248 | } 249 | }) 250 | }, 251 | ), 252 | ), 253 | Text( 254 | "${range.end.toInt()}", 255 | style: Theme.of(context).textTheme.headline6, 256 | ), 257 | ], 258 | ); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /test/date_widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:horizontal_calendar_widget/date_helper.dart'; 4 | import 'package:horizontal_calendar_widget/date_widget.dart'; 5 | 6 | void main() { 7 | final defaultLabelOrder = [ 8 | LabelType.month, 9 | LabelType.date, 10 | LabelType.weekday, 11 | ]; 12 | 13 | testWidgets( 14 | 'Render widgets with date / month/ weekday by default formats if formats are provided not explicitly', 15 | (WidgetTester tester) async { 16 | await tester.pumpWidget( 17 | Directionality( 18 | child: DateWidget( 19 | date: DateTime(2019, 11, 17), 20 | padding: EdgeInsets.all(8), 21 | labelOrder: defaultLabelOrder, 22 | ), 23 | textDirection: TextDirection.ltr, 24 | ), 25 | ); 26 | 27 | final date17 = find.text('17'); 28 | expect(date17, findsOneWidget); 29 | final month11 = find.text('Nov'); 30 | expect(month11, findsOneWidget); 31 | final week17 = find.text('Sun'); 32 | expect(week17, findsOneWidget); 33 | }); 34 | 35 | testWidgets( 36 | 'Render widgets with dates / month / week day by default formats if formats are provided explicitly', 37 | (WidgetTester tester) async { 38 | await tester.pumpWidget( 39 | Directionality( 40 | child: DateWidget( 41 | date: DateTime(2019, 11, 17), 42 | padding: EdgeInsets.all(8), 43 | dateFormat: 'dd/MMM', 44 | monthFormat: 'MM', 45 | weekDayFormat: 'EEEE', 46 | labelOrder: defaultLabelOrder, 47 | ), 48 | textDirection: TextDirection.ltr, 49 | ), 50 | ); 51 | 52 | final month11 = find.text('11'); 53 | expect(month11, findsOneWidget); 54 | final date17 = find.text('17/Nov'); 55 | expect(date17, findsOneWidget); 56 | final week17 = find.text('Sunday'); 57 | expect(week17, findsOneWidget); 58 | }, 59 | ); 60 | 61 | testWidgets( 62 | 'Default decoration should be applied to Date / month / weekday if not selected', 63 | (WidgetTester tester) async { 64 | final dateDecoration = BoxDecoration( 65 | color: Colors.blue, 66 | ); 67 | 68 | await tester.pumpWidget( 69 | Directionality( 70 | child: DateWidget( 71 | date: DateTime(2019, 11, 17), 72 | padding: EdgeInsets.all(8), 73 | dateFormat: null, 74 | monthFormat: null, 75 | weekDayFormat: null, 76 | defaultDecoration: dateDecoration, 77 | labelOrder: defaultLabelOrder, 78 | ), 79 | textDirection: TextDirection.ltr, 80 | ), 81 | ); 82 | 83 | WidgetPredicate datePredicate = (Widget widget) => 84 | widget is Container && widget.decoration == dateDecoration; 85 | expect(find.byWidgetPredicate(datePredicate), findsOneWidget); 86 | }, 87 | ); 88 | 89 | testWidgets( 90 | 'Selected decoration should be applied to Date / month / weekday is selected', 91 | (WidgetTester tester) async { 92 | final dateDecoration = BoxDecoration( 93 | color: Colors.blue, 94 | ); 95 | 96 | await tester.pumpWidget( 97 | Directionality( 98 | child: DateWidget( 99 | date: DateTime(2019, 11, 17), 100 | padding: EdgeInsets.all(8), 101 | dateFormat: null, 102 | monthFormat: null, 103 | weekDayFormat: null, 104 | selectedDecoration: dateDecoration, 105 | isSelected: true, 106 | labelOrder: defaultLabelOrder, 107 | ), 108 | textDirection: TextDirection.ltr, 109 | ), 110 | ); 111 | 112 | WidgetPredicate datePredicate = (Widget widget) => 113 | widget is Container && widget.decoration == dateDecoration; 114 | expect(find.byWidgetPredicate(datePredicate), findsOneWidget); 115 | }, 116 | ); 117 | 118 | testWidgets( 119 | 'Disabled decoration should be applied to Date / month / weekday if is disabled', 120 | (WidgetTester tester) async { 121 | final dateDecoration = BoxDecoration( 122 | color: Colors.blue, 123 | ); 124 | 125 | final style = TextStyle(color: Colors.white); 126 | 127 | await tester.pumpWidget( 128 | Directionality( 129 | child: DateWidget( 130 | date: DateTime(2019, 11, 17), 131 | padding: EdgeInsets.all(8), 132 | dateFormat: null, 133 | monthFormat: null, 134 | weekDayFormat: null, 135 | disabledDecoration: dateDecoration, 136 | isDisabled: true, 137 | dateTextStyle: style, 138 | labelOrder: defaultLabelOrder, 139 | ), 140 | textDirection: TextDirection.ltr, 141 | ), 142 | ); 143 | 144 | WidgetPredicate datePredicate = (Widget widget) => 145 | widget is Container && widget.decoration == dateDecoration; 146 | expect(find.byWidgetPredicate(datePredicate), findsOneWidget); 147 | 148 | WidgetPredicate dateTextStyle = 149 | (Widget widget) => widget is Text && widget.style == style; 150 | expect(find.byWidgetPredicate(dateTextStyle), findsOneWidget); 151 | }, 152 | ); 153 | 154 | testWidgets( 155 | 'Default Date with all properties', 156 | (WidgetTester tester) async { 157 | final defaultDecoration = BoxDecoration( 158 | color: Colors.blue, 159 | ); 160 | 161 | final padding = EdgeInsets.symmetric(horizontal: 8); 162 | 163 | final monthStyle = TextStyle(color: Colors.white); 164 | final monthFormat = 'MM'; 165 | 166 | final dateStyle = TextStyle(color: Colors.black); 167 | final dateFormat = 'dd/MM'; 168 | 169 | final weekDayStyle = TextStyle(color: Colors.green); 170 | final weekDayFormat = 'EEE'; 171 | 172 | await tester.pumpWidget( 173 | Directionality( 174 | child: DateWidget( 175 | date: DateTime(2019, 11, 17), 176 | padding: padding, 177 | dateFormat: dateFormat, 178 | dateTextStyle: dateStyle, 179 | monthFormat: monthFormat, 180 | monthTextStyle: monthStyle, 181 | weekDayFormat: weekDayFormat, 182 | weekDayTextStyle: weekDayStyle, 183 | defaultDecoration: defaultDecoration, 184 | labelOrder: defaultLabelOrder, 185 | ), 186 | textDirection: TextDirection.ltr, 187 | ), 188 | ); 189 | 190 | WidgetPredicate datePredicate = (Widget widget) => 191 | widget is Container && widget.decoration == defaultDecoration; 192 | expect(find.byWidgetPredicate(datePredicate), findsOneWidget); 193 | 194 | WidgetPredicate dateTextStyle = (Widget widget) => 195 | widget is Text && widget.style == dateStyle && widget.data == '17/11'; 196 | expect(find.byWidgetPredicate(dateTextStyle), findsOneWidget); 197 | 198 | WidgetPredicate monthTextStyle = (Widget widget) => 199 | widget is Text && widget.style == monthStyle && widget.data == '11'; 200 | expect(find.byWidgetPredicate(monthTextStyle), findsOneWidget); 201 | 202 | WidgetPredicate weekDayTextStyle = (Widget widget) => 203 | widget is Text && 204 | widget.style == weekDayStyle && 205 | widget.data == 'Sun'; 206 | expect(find.byWidgetPredicate(weekDayTextStyle), findsOneWidget); 207 | }, 208 | ); 209 | 210 | testWidgets( 211 | 'Lables should render as per provided order', 212 | (WidgetTester tester) async { 213 | await tester.pumpWidget( 214 | Directionality( 215 | child: DateWidget( 216 | date: DateTime(2019, 11, 17), 217 | padding: EdgeInsets.all(8), 218 | labelOrder: [ 219 | LabelType.date, 220 | LabelType.month, 221 | ], 222 | dateTextStyle: null, 223 | ), 224 | textDirection: TextDirection.ltr, 225 | ), 226 | ); 227 | 228 | WidgetPredicate columnPredicate = (Widget widget) { 229 | if (widget is Column) { 230 | final children = widget.children; 231 | if (children.length < 2) { 232 | return false; 233 | } 234 | Text firstText = children[0]; 235 | Text secondText = children[1]; 236 | if (firstText.data == "17" && secondText.data == "Nov") { 237 | return true; 238 | } 239 | } 240 | return false; 241 | }; 242 | 243 | expect(find.byWidgetPredicate(columnPredicate), findsOneWidget); 244 | }, 245 | ); 246 | } 247 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:horizontal_calendar_widget/date_helper.dart'; 5 | import 'package:horizontal_calendar_widget/horizontal_calendar.dart'; 6 | import 'package:intl/intl.dart'; 7 | 8 | import 'components/components.dart'; 9 | 10 | void main() => runApp(MyApp()); 11 | 12 | class MyApp extends StatelessWidget { 13 | @override 14 | Widget build(BuildContext context) { 15 | return MaterialApp( 16 | debugShowCheckedModeBanner: false, 17 | title: 'Flutter Horizontal Calendar Demo', 18 | theme: ThemeData( 19 | primarySwatch: Colors.blue, 20 | ), 21 | home: Scaffold( 22 | appBar: AppBar(title: Text('Horizontal Calendar Demo')), 23 | body: Padding( 24 | padding: const EdgeInsets.symmetric( 25 | horizontal: 16, 26 | ), 27 | child: DemoWidget(), 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | 34 | const labelMonth = 'Month'; 35 | const labelDate = 'Date'; 36 | const labelWeekDay = 'Week Day'; 37 | 38 | class DemoWidget extends StatefulWidget { 39 | const DemoWidget({ 40 | Key key, 41 | }) : super(key: key); 42 | 43 | @override 44 | _DemoWidgetState createState() => _DemoWidgetState(); 45 | } 46 | 47 | class _DemoWidgetState extends State { 48 | DateTime firstDate; 49 | DateTime lastDate; 50 | String dateFormat = 'dd'; 51 | String monthFormat = 'MMM'; 52 | String weekDayFormat = 'EEE'; 53 | List order = [labelMonth, labelDate, labelWeekDay]; 54 | bool forceRender = false; 55 | 56 | Color defaultDecorationColor = Colors.transparent; 57 | BoxShape defaultDecorationShape = BoxShape.rectangle; 58 | bool isCircularRadiusDefault = true; 59 | 60 | Color selectedDecorationColor = Colors.green; 61 | BoxShape selectedDecorationShape = BoxShape.rectangle; 62 | bool isCircularRadiusSelected = true; 63 | 64 | Color disabledDecorationColor = Colors.grey; 65 | BoxShape disabledDecorationShape = BoxShape.rectangle; 66 | bool isCircularRadiusDisabled = true; 67 | 68 | int minSelectedDateCount = 1; 69 | int maxSelectedDateCount = 1; 70 | RangeValues selectedDateCount; 71 | 72 | List initialSelectedDates; 73 | 74 | @override 75 | void initState() { 76 | super.initState(); 77 | const int days = 30; 78 | firstDate = toDateMonthYear(DateTime.now()); 79 | lastDate = toDateMonthYear(firstDate.add(Duration(days: days - 1))); 80 | selectedDateCount = RangeValues( 81 | minSelectedDateCount.toDouble(), 82 | maxSelectedDateCount.toDouble(), 83 | ); 84 | initialSelectedDates = feedInitialSelectedDates(minSelectedDateCount, days); 85 | } 86 | 87 | List feedInitialSelectedDates(int target, int calendarDays) { 88 | List selectedDates = List(); 89 | 90 | for (int i = 0; i < calendarDays; i++) { 91 | if (selectedDates.length == target) { 92 | break; 93 | } 94 | DateTime date = firstDate.add(Duration(days: i)); 95 | if (date.weekday != DateTime.sunday) { 96 | selectedDates.add(date); 97 | } 98 | } 99 | 100 | return selectedDates; 101 | } 102 | 103 | @override 104 | Widget build(BuildContext context) { 105 | return Column( 106 | crossAxisAlignment: CrossAxisAlignment.stretch, 107 | children: [ 108 | SizedBox(height: 16), 109 | HorizontalCalendar( 110 | key: forceRender ? UniqueKey() : Key('Calendar'), 111 | height: 120, 112 | padding: EdgeInsets.all(22), 113 | firstDate: firstDate, 114 | lastDate: lastDate, 115 | dateFormat: dateFormat, 116 | weekDayFormat: weekDayFormat, 117 | monthFormat: monthFormat, 118 | defaultDecoration: BoxDecoration( 119 | color: defaultDecorationColor, 120 | shape: defaultDecorationShape, 121 | borderRadius: defaultDecorationShape == BoxShape.rectangle && 122 | isCircularRadiusDefault 123 | ? BorderRadius.circular(8) 124 | : null, 125 | ), 126 | selectedDecoration: BoxDecoration( 127 | color: selectedDecorationColor, 128 | shape: selectedDecorationShape, 129 | borderRadius: selectedDecorationShape == BoxShape.rectangle && 130 | isCircularRadiusSelected 131 | ? BorderRadius.circular(8) 132 | : null, 133 | ), 134 | disabledDecoration: BoxDecoration( 135 | color: disabledDecorationColor, 136 | shape: disabledDecorationShape, 137 | borderRadius: disabledDecorationShape == BoxShape.rectangle && 138 | isCircularRadiusDisabled 139 | ? BorderRadius.circular(8) 140 | : null, 141 | ), 142 | isDateDisabled: (date) => date.weekday == DateTime.sunday, 143 | labelOrder: order.map(toLabelType).toList(), 144 | minSelectedDateCount: minSelectedDateCount, 145 | maxSelectedDateCount: maxSelectedDateCount, 146 | initialSelectedDates: initialSelectedDates, 147 | ), 148 | SizedBox(height: 32), 149 | Expanded( 150 | child: ListView( 151 | children: [ 152 | Header(headerText: 'Date Ranges'), 153 | Row( 154 | children: [ 155 | Expanded( 156 | child: PropertyLabel( 157 | label: 'First Date', 158 | value: Text(DateFormat('dd/MM/yyyy').format(firstDate)), 159 | onTap: () async { 160 | final date = await datePicker(context, firstDate); 161 | if (date == null) { 162 | return; 163 | } 164 | 165 | if (lastDate.isBefore(date)) { 166 | showMessage('First Date cannot be after Last Date'); 167 | return; 168 | } 169 | 170 | int min = minSelectedDateCount; 171 | if (!isRangeValid(date, lastDate, min)) { 172 | showMessage( 173 | "Date range is too low to set this configuration", 174 | ); 175 | return; 176 | } 177 | 178 | setState(() { 179 | forceRender = true; 180 | dateRangeChange(date, lastDate); 181 | }); 182 | }, 183 | ), 184 | ), 185 | Expanded( 186 | child: PropertyLabel( 187 | label: 'Last Date', 188 | value: Text(DateFormat('dd/MM/yyyy').format(lastDate)), 189 | onTap: () async { 190 | final date = await datePicker(context, lastDate); 191 | if (date == null) { 192 | return; 193 | } 194 | 195 | if (firstDate.isAfter(date)) { 196 | showMessage( 197 | 'Last Date cannot be before First Date', 198 | ); 199 | return; 200 | } 201 | 202 | int min = minSelectedDateCount; 203 | if (!isRangeValid(firstDate, date, min)) { 204 | showMessage( 205 | "Date range is too low to set this configuration", 206 | ); 207 | return; 208 | } 209 | 210 | setState(() { 211 | forceRender = true; 212 | dateRangeChange(firstDate, date); 213 | }); 214 | }, 215 | ), 216 | ), 217 | ], 218 | ), 219 | Header(headerText: 'Date Selection'), 220 | PropertyLabel( 221 | label: 222 | 'Min-Max Selectable Dates ($minSelectedDateCount - $maxSelectedDateCount)', 223 | value: CustomRangeSlider( 224 | range: selectedDateCount, 225 | min: 0, 226 | max: 15, 227 | onRangeSet: (newRange) { 228 | selectedDateCount = newRange; 229 | }, 230 | ), 231 | ), 232 | RaisedButton( 233 | child: Text('Update'), 234 | onPressed: () { 235 | setState(() { 236 | int min = selectedDateCount.start.toInt(); 237 | if (!isRangeValid(firstDate, lastDate, min)) { 238 | showMessage( 239 | "Date range is too low to set this configuration", 240 | ); 241 | return; 242 | } 243 | 244 | minSelectedDateCount = selectedDateCount.start.toInt(); 245 | maxSelectedDateCount = selectedDateCount.end.toInt(); 246 | initialSelectedDates = feedInitialSelectedDates( 247 | minSelectedDateCount, 248 | daysCount(firstDate, lastDate), 249 | ); 250 | showMessage("Updated"); 251 | }); 252 | }, 253 | ), 254 | Header(headerText: 'Formats'), 255 | PropertyLabel( 256 | label: 'Date Format', 257 | value: DropDownProperty( 258 | hint: 'Select Date Format', 259 | value: dateFormat, 260 | options: ['dd', 'dd/MM'], 261 | onChange: (format) { 262 | setState(() { 263 | forceRender = false; 264 | dateFormat = format; 265 | }); 266 | }, 267 | ), 268 | ), 269 | PropertyLabel( 270 | label: 'Month Format', 271 | value: DropDownProperty( 272 | hint: 'Select Month Format', 273 | value: monthFormat, 274 | options: [ 275 | 'MM', 276 | 'MMM', 277 | ], 278 | onChange: (format) { 279 | setState(() { 280 | forceRender = false; 281 | monthFormat = format; 282 | }); 283 | }, 284 | ), 285 | ), 286 | PropertyLabel( 287 | label: 'WeekDay Format', 288 | value: DropDownProperty( 289 | hint: 'Select Weekday Format', 290 | value: weekDayFormat, 291 | options: ['EEE', 'EEEE'], 292 | onChange: (format) { 293 | setState(() { 294 | forceRender = false; 295 | weekDayFormat = format; 296 | }); 297 | }, 298 | ), 299 | ), 300 | Header(headerText: 'Labels'), 301 | PropertyLabel( 302 | label: 'Label Orders (Drag & Drop to reorder)', 303 | value: Align( 304 | alignment: Alignment.centerLeft, 305 | child: Row( 306 | children: [ 307 | SizedBox( 308 | height: 200, 309 | width: 150, 310 | child: ReorderableListView( 311 | children: order 312 | .map( 313 | (listItem) => Align( 314 | key: Key(listItem), 315 | heightFactor: 1, 316 | alignment: Alignment.centerLeft, 317 | child: Chip( 318 | onDeleted: () => listItem != labelDate 319 | ? setState(() { 320 | forceRender = false; 321 | order.remove(listItem); 322 | }) 323 | : null, 324 | deleteIcon: listItem != labelDate 325 | ? Icon(Icons.cancel) 326 | : null, 327 | label: Text(listItem), 328 | ), 329 | ), 330 | ) 331 | .toList(), 332 | onReorder: (oldIndex, newIndex) { 333 | setState(() { 334 | forceRender = false; 335 | if (newIndex > oldIndex) { 336 | newIndex -= 1; 337 | } 338 | final item = order.removeAt(oldIndex); 339 | order.insert(newIndex, item); 340 | }); 341 | }, 342 | ), 343 | ), 344 | RaisedButton( 345 | child: Text('Add Labels'), 346 | onPressed: () { 347 | setState(() { 348 | forceRender = false; 349 | if (!order.contains(labelMonth)) { 350 | order.add(labelMonth); 351 | } 352 | if (!order.contains(labelWeekDay)) { 353 | order.add(labelWeekDay); 354 | } 355 | }); 356 | }, 357 | ) 358 | ], 359 | ), 360 | ), 361 | ), 362 | Header(headerText: 'Default Decoration'), 363 | DecorationBuilder( 364 | decorationShape: defaultDecorationShape, 365 | onSelectShape: (value) { 366 | setState(() { 367 | forceRender = false; 368 | defaultDecorationShape = value; 369 | }); 370 | }, 371 | isCircularRadius: isCircularRadiusDefault, 372 | onCircularRadiusChange: (isSelected) { 373 | setState( 374 | () { 375 | isCircularRadiusDefault = isSelected; 376 | }, 377 | ); 378 | }, 379 | color: defaultDecorationColor, 380 | onColorChange: (value) { 381 | setState(() { 382 | forceRender = false; 383 | defaultDecorationColor = value; 384 | }); 385 | }, 386 | ), 387 | Header(headerText: 'Selected Decoration'), 388 | DecorationBuilder( 389 | decorationShape: selectedDecorationShape, 390 | onSelectShape: (value) { 391 | setState(() { 392 | forceRender = false; 393 | selectedDecorationShape = value; 394 | }); 395 | }, 396 | isCircularRadius: isCircularRadiusSelected, 397 | onCircularRadiusChange: (isSelected) { 398 | setState( 399 | () { 400 | forceRender = false; 401 | isCircularRadiusSelected = isSelected; 402 | }, 403 | ); 404 | }, 405 | color: selectedDecorationColor, 406 | onColorChange: (value) { 407 | setState(() { 408 | forceRender = false; 409 | selectedDecorationColor = value; 410 | }); 411 | }, 412 | ), 413 | Header(headerText: 'Disabled Decoration'), 414 | DecorationBuilder( 415 | decorationShape: disabledDecorationShape, 416 | onSelectShape: (value) { 417 | setState(() { 418 | forceRender = false; 419 | disabledDecorationShape = value; 420 | }); 421 | }, 422 | isCircularRadius: isCircularRadiusDisabled, 423 | onCircularRadiusChange: (isSelected) { 424 | setState( 425 | () { 426 | forceRender = false; 427 | isCircularRadiusDisabled = isSelected; 428 | }, 429 | ); 430 | }, 431 | color: disabledDecorationColor, 432 | onColorChange: (value) { 433 | setState(() { 434 | forceRender = false; 435 | disabledDecorationColor = value; 436 | }); 437 | }, 438 | ) 439 | ], 440 | ), 441 | ) 442 | ], 443 | ); 444 | } 445 | 446 | void showMessage(String message) { 447 | Scaffold.of(context).showSnackBar( 448 | SnackBar(content: Text(message)), 449 | ); 450 | } 451 | 452 | bool isRangeValid(DateTime first, DateTime last, int minSelection) { 453 | int availableDays = availableDaysCount( 454 | getDateList(first, last), 455 | [DateTime.sunday], 456 | ); 457 | 458 | return availableDays >= minSelection; 459 | } 460 | 461 | int availableDaysCount(List dates, List disabledDays) => 462 | dates.where((date) => !disabledDays.contains(date.weekday)).length; 463 | 464 | void dateRangeChange(DateTime first, DateTime last) { 465 | firstDate = first; 466 | lastDate = last; 467 | initialSelectedDates = feedInitialSelectedDates( 468 | minSelectedDateCount, 469 | daysCount(first, last), 470 | ); 471 | selectedDateCount = RangeValues( 472 | minSelectedDateCount.toDouble(), 473 | maxSelectedDateCount.toDouble(), 474 | ); 475 | } 476 | } 477 | 478 | Future datePicker( 479 | BuildContext context, 480 | DateTime initialDate, 481 | ) async { 482 | final selectedDate = await showDatePicker( 483 | context: context, 484 | initialDate: initialDate, 485 | firstDate: DateTime.now().subtract( 486 | Duration(days: 365), 487 | ), 488 | lastDate: DateTime.now().add( 489 | Duration(days: 365), 490 | ), 491 | ); 492 | return toDateMonthYear(selectedDate); 493 | } 494 | 495 | LabelType toLabelType(String label) { 496 | LabelType type; 497 | switch (label) { 498 | case labelMonth: 499 | type = LabelType.month; 500 | break; 501 | case labelDate: 502 | type = LabelType.date; 503 | break; 504 | case labelWeekDay: 505 | type = LabelType.weekday; 506 | break; 507 | } 508 | return type; 509 | } 510 | 511 | String fromLabelType(LabelType label) { 512 | String labelString; 513 | switch (label) { 514 | case LabelType.month: 515 | labelString = labelMonth; 516 | break; 517 | case LabelType.date: 518 | labelString = labelDate; 519 | break; 520 | case LabelType.weekday: 521 | labelString = labelWeekDay; 522 | break; 523 | } 524 | return labelString; 525 | } 526 | -------------------------------------------------------------------------------- /test/horizontal_calendar_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:horizontal_calendar_widget/horizontal_calendar.dart'; 5 | 6 | void main() { 7 | testWidgets( 8 | 'Assert should fail if firstDate is NULL', 9 | (WidgetTester tester) async { 10 | expectLater( 11 | () => tester.pumpWidget( 12 | Directionality( 13 | child: HorizontalCalendar( 14 | firstDate: null, 15 | lastDate: DateTime(2019, 11, 20), 16 | ), 17 | textDirection: TextDirection.ltr, 18 | ), 19 | ), 20 | throwsAssertionError); 21 | }, 22 | ); 23 | 24 | testWidgets( 25 | 'Assert should fail if lastDate is NULL', 26 | (WidgetTester tester) async { 27 | expectLater( 28 | () => tester.pumpWidget( 29 | Directionality( 30 | child: HorizontalCalendar( 31 | firstDate: DateTime(2019, 11, 20), 32 | lastDate: null, 33 | ), 34 | textDirection: TextDirection.ltr, 35 | ), 36 | ), 37 | throwsAssertionError); 38 | }, 39 | ); 40 | 41 | testWidgets( 42 | 'Assert should fail if Label order is NULL', 43 | (WidgetTester tester) async { 44 | expectLater( 45 | () => tester.pumpWidget( 46 | Directionality( 47 | child: HorizontalCalendar( 48 | firstDate: DateTime(2019, 11, 20), 49 | lastDate: null, 50 | labelOrder: null, 51 | ), 52 | textDirection: TextDirection.ltr, 53 | ), 54 | ), 55 | throwsAssertionError); 56 | }, 57 | ); 58 | 59 | testWidgets( 60 | 'Assert should fail if Label order is empty', 61 | (WidgetTester tester) async { 62 | expectLater( 63 | () => tester.pumpWidget( 64 | Directionality( 65 | child: HorizontalCalendar( 66 | firstDate: DateTime(2019, 11, 20), 67 | lastDate: null, 68 | labelOrder: [], 69 | ), 70 | textDirection: TextDirection.ltr, 71 | ), 72 | ), 73 | throwsAssertionError); 74 | }, 75 | ); 76 | 77 | testWidgets( 78 | 'Assert should fail if minSelectedDateCount is -gt maxSelectedDateCount', 79 | (WidgetTester tester) async { 80 | expectLater( 81 | () => tester.pumpWidget( 82 | Directionality( 83 | child: HorizontalCalendar( 84 | firstDate: DateTime(2020, 03, 04), 85 | lastDate: DateTime(2020, 03, 08), 86 | minSelectedDateCount: 2, 87 | maxSelectedDateCount: 1, 88 | ), 89 | textDirection: TextDirection.ltr, 90 | ), 91 | ), 92 | throwsAssertionError); 93 | }, 94 | ); 95 | 96 | testWidgets( 97 | 'Assert should fail if initialSelectedDate lenght is -lt minSelectedDateCount', 98 | (WidgetTester tester) async { 99 | expectLater( 100 | () => tester.pumpWidget( 101 | Directionality( 102 | child: HorizontalCalendar( 103 | firstDate: DateTime(2020, 03, 04), 104 | lastDate: DateTime(2020, 03, 08), 105 | minSelectedDateCount: 2, 106 | maxSelectedDateCount: 3, 107 | initialSelectedDates: [ 108 | DateTime(2020, 03, 04), 109 | ], 110 | ), 111 | textDirection: TextDirection.ltr, 112 | ), 113 | ), 114 | throwsAssertionError); 115 | }, 116 | ); 117 | 118 | testWidgets( 119 | 'Assert should fail if initialSelectedDate lenght is -gt maxSelectedDateCount', 120 | (WidgetTester tester) async { 121 | expectLater( 122 | () => tester.pumpWidget( 123 | Directionality( 124 | child: HorizontalCalendar( 125 | firstDate: DateTime(2020, 03, 04), 126 | lastDate: DateTime(2020, 03, 08), 127 | maxSelectedDateCount: 2, 128 | initialSelectedDates: [ 129 | DateTime(2020, 03, 04), 130 | DateTime(2020, 03, 05), 131 | DateTime(2020, 03, 06), 132 | ], 133 | ), 134 | textDirection: TextDirection.ltr, 135 | ), 136 | ), 137 | throwsAssertionError); 138 | }, 139 | ); 140 | 141 | testWidgets('Should render N widgets as provided start & end date', 142 | (WidgetTester tester) async { 143 | await tester.pumpWidget( 144 | Directionality( 145 | child: HorizontalCalendar( 146 | firstDate: DateTime(2019, 11, 17), 147 | lastDate: DateTime(2019, 11, 20), 148 | monthFormat: 'MMM', 149 | weekDayFormat: 'EEE', 150 | ), 151 | textDirection: TextDirection.ltr, 152 | ), 153 | ); 154 | 155 | final month11 = find.text('Nov'); 156 | expect(month11, findsNWidgets(4)); 157 | 158 | final date17 = find.text('17'); 159 | expect(date17, findsOneWidget); 160 | final week17 = find.text('Sun'); 161 | expect(week17, findsOneWidget); 162 | 163 | final date18 = find.text('18'); 164 | expect(date18, findsOneWidget); 165 | final week18 = find.text('Mon'); 166 | expect(week18, findsOneWidget); 167 | 168 | final date19 = find.text('19'); 169 | expect(date19, findsOneWidget); 170 | final week19 = find.text('Tue'); 171 | expect(week19, findsOneWidget); 172 | }); 173 | 174 | testWidgets( 175 | 'Default / Selected / Disabled widgets should render as per arguments', 176 | (WidgetTester tester) async { 177 | final dateDecoration = BoxDecoration( 178 | color: Colors.blue, 179 | ); 180 | final selectedDateDecoration = BoxDecoration( 181 | color: Colors.green, 182 | ); 183 | 184 | final disabledDateDecoration = BoxDecoration( 185 | color: Colors.grey, 186 | ); 187 | 188 | await tester.pumpWidget( 189 | Directionality( 190 | child: HorizontalCalendar( 191 | firstDate: DateTime(2019, 11, 17), 192 | lastDate: DateTime(2019, 11, 19), 193 | defaultDecoration: dateDecoration, 194 | selectedDecoration: selectedDateDecoration, 195 | initialSelectedDates: [DateTime(2019, 11, 18)], 196 | disabledDecoration: disabledDateDecoration, 197 | isDateDisabled: (date) => 198 | date.compareTo(DateTime(2019, 11, 19)) == 0, 199 | ), 200 | textDirection: TextDirection.ltr, 201 | ), 202 | ); 203 | 204 | WidgetPredicate datePredicate = (Widget widget) => 205 | widget is Container && widget.decoration == dateDecoration; 206 | WidgetPredicate selectedDatePredicate = (Widget widget) => 207 | widget is Container && widget.decoration == selectedDateDecoration; 208 | WidgetPredicate disabledDatePredicate = (Widget widget) => 209 | widget is Container && widget.decoration == disabledDateDecoration; 210 | 211 | expect(find.byWidgetPredicate(datePredicate), findsOneWidget); 212 | expect(find.byWidgetPredicate(selectedDatePredicate), findsOneWidget); 213 | expect(find.byWidgetPredicate(disabledDatePredicate), findsOneWidget); 214 | }, 215 | ); 216 | 217 | testWidgets( 218 | 'State should change after tapping any of date', 219 | (WidgetTester tester) async { 220 | final dateDecoration = BoxDecoration( 221 | color: Colors.blue, 222 | ); 223 | final selectedDateDecoration = BoxDecoration( 224 | color: Colors.green, 225 | ); 226 | 227 | final disabledDateDecoration = BoxDecoration( 228 | color: Colors.grey, 229 | ); 230 | 231 | await tester.pumpWidget( 232 | Directionality( 233 | child: HorizontalCalendar( 234 | firstDate: DateTime(2019, 11, 17), 235 | lastDate: DateTime(2019, 11, 19), 236 | defaultDecoration: dateDecoration, 237 | selectedDecoration: selectedDateDecoration, 238 | initialSelectedDates: [DateTime(2019, 11, 18)], 239 | disabledDecoration: disabledDateDecoration, 240 | isDateDisabled: (date) => 241 | date.compareTo(DateTime(2019, 11, 19)) == 0, 242 | onDateSelected: (date) => print("S:$date"), 243 | onDateUnSelected: (date) => print("U:$date"), 244 | onDateLongTap: (date) => print("L:$date"), 245 | maxSelectedDateCount: 2, 246 | ), 247 | textDirection: TextDirection.ltr, 248 | ), 249 | ); 250 | 251 | WidgetPredicate datePredicate = (Widget widget) => 252 | widget is Container && widget.decoration == dateDecoration; 253 | WidgetPredicate selectedDatePredicate = (Widget widget) => 254 | widget is Container && widget.decoration == selectedDateDecoration; 255 | WidgetPredicate disabledDatePredicate = (Widget widget) => 256 | widget is Container && widget.decoration == disabledDateDecoration; 257 | 258 | expect(find.byWidgetPredicate(datePredicate), findsOneWidget); 259 | expect(find.byWidgetPredicate(selectedDatePredicate), findsOneWidget); 260 | expect(find.byWidgetPredicate(disabledDatePredicate), findsOneWidget); 261 | 262 | await tester 263 | .tap(find.byKey(Key(DateTime(2019, 11, 17).toIso8601String()))); 264 | 265 | await tester.pumpAndSettle(); 266 | expect(find.byWidgetPredicate(selectedDatePredicate), findsNWidgets(2)); 267 | 268 | await tester 269 | .tap(find.byKey(Key(DateTime(2019, 11, 17).toIso8601String()))); 270 | 271 | await tester.pumpAndSettle(); 272 | expect(find.byWidgetPredicate(selectedDatePredicate), findsOneWidget); 273 | }, 274 | ); 275 | 276 | testWidgets( 277 | '''Case: maxSelectedDateCount = 1. 278 | only one date should be selected at a time. 279 | On select new date, previous date should be unselected''', 280 | (WidgetTester tester) async { 281 | final dateDecoration = BoxDecoration( 282 | color: Colors.blue, 283 | ); 284 | final selectedDateDecoration = BoxDecoration( 285 | color: Colors.green, 286 | ); 287 | 288 | final disabledDateDecoration = BoxDecoration( 289 | color: Colors.grey, 290 | ); 291 | 292 | await tester.pumpWidget( 293 | Directionality( 294 | child: HorizontalCalendar( 295 | firstDate: DateTime(2019, 11, 17), 296 | lastDate: DateTime(2019, 11, 19), 297 | defaultDecoration: dateDecoration, 298 | selectedDecoration: selectedDateDecoration, 299 | disabledDecoration: disabledDateDecoration, 300 | isDateDisabled: (date) => 301 | date.compareTo(DateTime(2019, 11, 19)) == 0, 302 | onDateSelected: (date) => print("S:$date"), 303 | onDateUnSelected: (date) => print("U:$date"), 304 | onDateLongTap: (date) => print("L:$date"), 305 | maxSelectedDateCount: 1, 306 | ), 307 | textDirection: TextDirection.ltr, 308 | ), 309 | ); 310 | 311 | WidgetPredicate selectedDatePredicate = (Widget widget) => 312 | widget is Container && widget.decoration == selectedDateDecoration; 313 | 314 | //Tap date 17 and expect only single date should be selected 315 | await tester 316 | .tap(find.byKey(Key(DateTime(2019, 11, 17).toIso8601String()))); 317 | await tester.pumpAndSettle(); 318 | expect(find.byWidgetPredicate(selectedDatePredicate), findsOneWidget); 319 | 320 | //Again tap 17 and expect it should be un selected 321 | await tester 322 | .tap(find.byKey(Key(DateTime(2019, 11, 17).toIso8601String()))); 323 | await tester.pumpAndSettle(); 324 | expect(find.byWidgetPredicate(selectedDatePredicate), findsNothing); 325 | 326 | //Tap 17 and then 18 and expect only one date should be selected 327 | await tester 328 | .tap(find.byKey(Key(DateTime(2019, 11, 17).toIso8601String()))); 329 | await tester.pumpAndSettle(); 330 | await tester 331 | .tap(find.byKey(Key(DateTime(2019, 11, 18).toIso8601String()))); 332 | await tester.pumpAndSettle(); 333 | expect(find.byWidgetPredicate(selectedDatePredicate), findsOneWidget); 334 | }, 335 | ); 336 | 337 | testWidgets( 338 | '''Case: maxSelectedDateCount = 2. 339 | only two dates should be selected at a time. 340 | If max count reached, new dates should not be selected''', 341 | (WidgetTester tester) async { 342 | final dateDecoration = BoxDecoration( 343 | color: Colors.blue, 344 | ); 345 | final selectedDateDecoration = BoxDecoration( 346 | color: Colors.green, 347 | ); 348 | 349 | final disabledDateDecoration = BoxDecoration( 350 | color: Colors.grey, 351 | ); 352 | 353 | await tester.pumpWidget( 354 | Directionality( 355 | child: HorizontalCalendar( 356 | firstDate: DateTime(2019, 11, 17), 357 | lastDate: DateTime(2019, 11, 19), 358 | defaultDecoration: dateDecoration, 359 | selectedDecoration: selectedDateDecoration, 360 | disabledDecoration: disabledDateDecoration, 361 | onDateSelected: (date) => print("S:$date"), 362 | onDateUnSelected: (date) => print("U:$date"), 363 | onDateLongTap: (date) => print("L:$date"), 364 | maxSelectedDateCount: 2, 365 | ), 366 | textDirection: TextDirection.ltr, 367 | ), 368 | ); 369 | 370 | WidgetPredicate selectedDatePredicate = (Widget widget) => 371 | widget is Container && widget.decoration == selectedDateDecoration; 372 | 373 | //Tap 17 and then 18 and expect two dates should be selected 374 | await tester 375 | .tap(find.byKey(Key(DateTime(2019, 11, 17).toIso8601String()))); 376 | await tester.pumpAndSettle(); 377 | await tester 378 | .tap(find.byKey(Key(DateTime(2019, 11, 18).toIso8601String()))); 379 | await tester.pumpAndSettle(); 380 | expect(find.byWidgetPredicate(selectedDatePredicate), findsNWidgets(2)); 381 | 382 | //Tap 19 and expect two dates should be selected: 19 should not be selected 383 | await tester 384 | .tap(find.byKey(Key(DateTime(2019, 11, 19).toIso8601String()))); 385 | await tester.pumpAndSettle(); 386 | expect(find.byWidgetPredicate(selectedDatePredicate), findsNWidgets(2)); 387 | }, 388 | ); 389 | 390 | testWidgets( 391 | '''Case: minSelectedDateCount = 1 and maxSelectedDateCount = 1. 392 | one and only one date should be always selected.''', 393 | (WidgetTester tester) async { 394 | final dateDecoration = BoxDecoration( 395 | color: Colors.blue, 396 | ); 397 | final selectedDateDecoration = BoxDecoration( 398 | color: Colors.green, 399 | ); 400 | 401 | final disabledDateDecoration = BoxDecoration( 402 | color: Colors.grey, 403 | ); 404 | 405 | await tester.pumpWidget( 406 | Directionality( 407 | child: HorizontalCalendar( 408 | firstDate: DateTime(2020, 03, 04), 409 | lastDate: DateTime(2020, 03, 08), 410 | defaultDecoration: dateDecoration, 411 | selectedDecoration: selectedDateDecoration, 412 | disabledDecoration: disabledDateDecoration, 413 | onDateSelected: (date) => print("S:$date"), 414 | onDateUnSelected: (date) => print("U:$date"), 415 | onDateLongTap: (date) => print("L:$date"), 416 | minSelectedDateCount: 1, 417 | maxSelectedDateCount: 1, 418 | initialSelectedDates: [ 419 | DateTime(2020, 03, 05), 420 | ], 421 | ), 422 | textDirection: TextDirection.ltr, 423 | ), 424 | ); 425 | 426 | WidgetPredicate selectedDatePredicate = (Widget widget) => 427 | widget is Container && widget.decoration == selectedDateDecoration; 428 | 429 | //Tap 04 and expect one date still being selected 430 | await tester 431 | .tap(find.byKey(Key(DateTime(2020, 03, 04).toIso8601String()))); 432 | await tester.pumpAndSettle(); 433 | expect(find.byWidgetPredicate(selectedDatePredicate), findsNWidgets(1)); 434 | 435 | //Tap 05 and expect still only one date being selected 436 | await tester 437 | .tap(find.byKey(Key(DateTime(2020, 03, 05).toIso8601String()))); 438 | await tester.pumpAndSettle(); 439 | expect(find.byWidgetPredicate(selectedDatePredicate), findsNWidgets(1)); 440 | }, 441 | ); 442 | 443 | testWidgets( 444 | 'onDateSelected callback should be invoked', 445 | (WidgetTester tester) async { 446 | await tester.pumpWidget( 447 | Directionality( 448 | child: HorizontalCalendar( 449 | firstDate: DateTime(2019, 11, 17), 450 | lastDate: DateTime(2019, 11, 19), 451 | initialSelectedDates: [DateTime(2019, 11, 18)], 452 | isDateDisabled: (date) => 453 | date.compareTo(DateTime(2019, 11, 19)) == 0, 454 | onDateSelected: (date) => print("S:$date"), 455 | onDateUnSelected: (date) => print("U:$date"), 456 | onDateLongTap: (date) => print("L:$date"), 457 | ), 458 | textDirection: TextDirection.ltr, 459 | ), 460 | ); 461 | 462 | expectLater( 463 | () => tester 464 | .tap(find.byKey(Key(DateTime(2019, 11, 17).toIso8601String()))), 465 | prints('S:${DateTime(2019, 11, 17)}\n'), 466 | ); 467 | }, 468 | ); 469 | 470 | testWidgets( 471 | 'onDateUnSelected callback should be invoked', 472 | (WidgetTester tester) async { 473 | await tester.pumpWidget( 474 | Directionality( 475 | child: HorizontalCalendar( 476 | firstDate: DateTime(2019, 11, 17), 477 | lastDate: DateTime(2019, 11, 19), 478 | initialSelectedDates: [DateTime(2019, 11, 18)], 479 | isDateDisabled: (date) => 480 | date.compareTo(DateTime(2019, 11, 19)) == 0, 481 | onDateSelected: (date) => print("S:$date"), 482 | onDateUnSelected: (date) => print("U:$date"), 483 | onDateLongTap: (date) => print("L:$date"), 484 | ), 485 | textDirection: TextDirection.ltr, 486 | ), 487 | ); 488 | 489 | expectLater( 490 | () => tester 491 | .tap(find.byKey(Key(DateTime(2019, 11, 18).toIso8601String()))), 492 | prints('U:${DateTime(2019, 11, 18)}\n'), 493 | ); 494 | }, 495 | ); 496 | 497 | testWidgets( 498 | 'onDateLongTap callback should be invoked', 499 | (WidgetTester tester) async { 500 | await tester.pumpWidget( 501 | Directionality( 502 | child: HorizontalCalendar( 503 | firstDate: DateTime(2019, 11, 17), 504 | lastDate: DateTime(2019, 11, 19), 505 | initialSelectedDates: [DateTime(2019, 11, 18)], 506 | isDateDisabled: (date) => 507 | date.compareTo(DateTime(2019, 11, 19)) == 0, 508 | onDateSelected: (date) => print("S:$date"), 509 | onDateUnSelected: (date) => print("U:$date"), 510 | onDateLongTap: (date) => print("L:$date"), 511 | ), 512 | textDirection: TextDirection.ltr, 513 | ), 514 | ); 515 | 516 | expectLater( 517 | () => tester.longPress( 518 | find.byKey(Key(DateTime(2019, 11, 18).toIso8601String()))), 519 | prints('L:${DateTime(2019, 11, 18)}\n'), 520 | ); 521 | }, 522 | ); 523 | 524 | testWidgets( 525 | 'onMaxDateSelectionReached callback should be invoked', 526 | (WidgetTester tester) async { 527 | await tester.pumpWidget( 528 | Directionality( 529 | child: HorizontalCalendar( 530 | firstDate: DateTime(2019, 11, 17), 531 | lastDate: DateTime(2019, 11, 19), 532 | isDateDisabled: (date) => 533 | date.compareTo(DateTime(2019, 11, 19)) == 0, 534 | onMaxDateSelectionReached: () => print("MAX_SELCTION"), 535 | maxSelectedDateCount: 0, 536 | ), 537 | textDirection: TextDirection.ltr, 538 | ), 539 | ); 540 | 541 | expectLater( 542 | () => tester 543 | .tap(find.byKey(Key(DateTime(2019, 11, 17).toIso8601String()))), 544 | prints('MAX_SELCTION\n'), 545 | ); 546 | }, 547 | ); 548 | } 549 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 18 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 19 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 20 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXCopyFilesBuildPhase section */ 24 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 25 | isa = PBXCopyFilesBuildPhase; 26 | buildActionMask = 2147483647; 27 | dstPath = ""; 28 | dstSubfolderSpec = 10; 29 | files = ( 30 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 31 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 32 | ); 33 | name = "Embed Frameworks"; 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXCopyFilesBuildPhase section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 40 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 41 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 42 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 43 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 44 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 46 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 47 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 48 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 49 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 53 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 62 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 9740EEB11CF90186004384FC /* Flutter */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 3B80C3931E831B6300D905FE /* App.framework */, 73 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 74 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 75 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 76 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 77 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 78 | ); 79 | name = Flutter; 80 | sourceTree = ""; 81 | }; 82 | 97C146E51CF9000F007C117D = { 83 | isa = PBXGroup; 84 | children = ( 85 | 9740EEB11CF90186004384FC /* Flutter */, 86 | 97C146F01CF9000F007C117D /* Runner */, 87 | 97C146EF1CF9000F007C117D /* Products */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | 97C146EF1CF9000F007C117D /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 97C146EE1CF9000F007C117D /* Runner.app */, 95 | ); 96 | name = Products; 97 | sourceTree = ""; 98 | }; 99 | 97C146F01CF9000F007C117D /* Runner */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 103 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 104 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 105 | 97C147021CF9000F007C117D /* Info.plist */, 106 | 97C146F11CF9000F007C117D /* Supporting Files */, 107 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 108 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 109 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 110 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 111 | ); 112 | path = Runner; 113 | sourceTree = ""; 114 | }; 115 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | ); 119 | name = "Supporting Files"; 120 | sourceTree = ""; 121 | }; 122 | /* End PBXGroup section */ 123 | 124 | /* Begin PBXNativeTarget section */ 125 | 97C146ED1CF9000F007C117D /* Runner */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 128 | buildPhases = ( 129 | 9740EEB61CF901F6004384FC /* Run Script */, 130 | 97C146EA1CF9000F007C117D /* Sources */, 131 | 97C146EB1CF9000F007C117D /* Frameworks */, 132 | 97C146EC1CF9000F007C117D /* Resources */, 133 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 134 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | ); 140 | name = Runner; 141 | productName = Runner; 142 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 143 | productType = "com.apple.product-type.application"; 144 | }; 145 | /* End PBXNativeTarget section */ 146 | 147 | /* Begin PBXProject section */ 148 | 97C146E61CF9000F007C117D /* Project object */ = { 149 | isa = PBXProject; 150 | attributes = { 151 | LastUpgradeCheck = 1020; 152 | ORGANIZATIONNAME = "The Chromium Authors"; 153 | TargetAttributes = { 154 | 97C146ED1CF9000F007C117D = { 155 | CreatedOnToolsVersion = 7.3.1; 156 | LastSwiftMigration = 0910; 157 | }; 158 | }; 159 | }; 160 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 161 | compatibilityVersion = "Xcode 3.2"; 162 | developmentRegion = en; 163 | hasScannedForEncodings = 0; 164 | knownRegions = ( 165 | en, 166 | Base, 167 | ); 168 | mainGroup = 97C146E51CF9000F007C117D; 169 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 170 | projectDirPath = ""; 171 | projectRoot = ""; 172 | targets = ( 173 | 97C146ED1CF9000F007C117D /* Runner */, 174 | ); 175 | }; 176 | /* End PBXProject section */ 177 | 178 | /* Begin PBXResourcesBuildPhase section */ 179 | 97C146EC1CF9000F007C117D /* Resources */ = { 180 | isa = PBXResourcesBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 184 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 185 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 186 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 187 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXResourcesBuildPhase section */ 192 | 193 | /* Begin PBXShellScriptBuildPhase section */ 194 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | ); 201 | name = "Thin Binary"; 202 | outputPaths = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 207 | }; 208 | 9740EEB61CF901F6004384FC /* Run Script */ = { 209 | isa = PBXShellScriptBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | inputPaths = ( 214 | ); 215 | name = "Run Script"; 216 | outputPaths = ( 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | shellPath = /bin/sh; 220 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 221 | }; 222 | /* End PBXShellScriptBuildPhase section */ 223 | 224 | /* Begin PBXSourcesBuildPhase section */ 225 | 97C146EA1CF9000F007C117D /* Sources */ = { 226 | isa = PBXSourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 230 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXSourcesBuildPhase section */ 235 | 236 | /* Begin PBXVariantGroup section */ 237 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 238 | isa = PBXVariantGroup; 239 | children = ( 240 | 97C146FB1CF9000F007C117D /* Base */, 241 | ); 242 | name = Main.storyboard; 243 | sourceTree = ""; 244 | }; 245 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 246 | isa = PBXVariantGroup; 247 | children = ( 248 | 97C147001CF9000F007C117D /* Base */, 249 | ); 250 | name = LaunchScreen.storyboard; 251 | sourceTree = ""; 252 | }; 253 | /* End PBXVariantGroup section */ 254 | 255 | /* Begin XCBuildConfiguration section */ 256 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 257 | isa = XCBuildConfiguration; 258 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 259 | buildSettings = { 260 | ALWAYS_SEARCH_USER_PATHS = NO; 261 | CLANG_ANALYZER_NONNULL = YES; 262 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 263 | CLANG_CXX_LIBRARY = "libc++"; 264 | CLANG_ENABLE_MODULES = YES; 265 | CLANG_ENABLE_OBJC_ARC = YES; 266 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 267 | CLANG_WARN_BOOL_CONVERSION = YES; 268 | CLANG_WARN_COMMA = YES; 269 | CLANG_WARN_CONSTANT_CONVERSION = YES; 270 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_EMPTY_BODY = YES; 273 | CLANG_WARN_ENUM_CONVERSION = YES; 274 | CLANG_WARN_INFINITE_RECURSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 278 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 279 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 280 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 281 | CLANG_WARN_STRICT_PROTOTYPES = YES; 282 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 283 | CLANG_WARN_UNREACHABLE_CODE = YES; 284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 285 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 286 | COPY_PHASE_STRIP = NO; 287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | GCC_C_LANGUAGE_STANDARD = gnu99; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 299 | MTL_ENABLE_DEBUG_INFO = NO; 300 | SDKROOT = iphoneos; 301 | TARGETED_DEVICE_FAMILY = "1,2"; 302 | VALIDATE_PRODUCT = YES; 303 | }; 304 | name = Profile; 305 | }; 306 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 307 | isa = XCBuildConfiguration; 308 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | CLANG_ENABLE_MODULES = YES; 312 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 313 | ENABLE_BITCODE = NO; 314 | FRAMEWORK_SEARCH_PATHS = ( 315 | "$(inherited)", 316 | "$(PROJECT_DIR)/Flutter", 317 | ); 318 | INFOPLIST_FILE = Runner/Info.plist; 319 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 320 | LIBRARY_SEARCH_PATHS = ( 321 | "$(inherited)", 322 | "$(PROJECT_DIR)/Flutter", 323 | ); 324 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 327 | SWIFT_VERSION = 4.0; 328 | VERSIONING_SYSTEM = "apple-generic"; 329 | }; 330 | name = Profile; 331 | }; 332 | 97C147031CF9000F007C117D /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | CLANG_ANALYZER_NONNULL = YES; 338 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 339 | CLANG_CXX_LIBRARY = "libc++"; 340 | CLANG_ENABLE_MODULES = YES; 341 | CLANG_ENABLE_OBJC_ARC = YES; 342 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 343 | CLANG_WARN_BOOL_CONVERSION = YES; 344 | CLANG_WARN_COMMA = YES; 345 | CLANG_WARN_CONSTANT_CONVERSION = YES; 346 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 347 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 348 | CLANG_WARN_EMPTY_BODY = YES; 349 | CLANG_WARN_ENUM_CONVERSION = YES; 350 | CLANG_WARN_INFINITE_RECURSION = YES; 351 | CLANG_WARN_INT_CONVERSION = YES; 352 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 353 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 354 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 356 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 357 | CLANG_WARN_STRICT_PROTOTYPES = YES; 358 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 359 | CLANG_WARN_UNREACHABLE_CODE = YES; 360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 361 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 362 | COPY_PHASE_STRIP = NO; 363 | DEBUG_INFORMATION_FORMAT = dwarf; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | ENABLE_TESTABILITY = YES; 366 | GCC_C_LANGUAGE_STANDARD = gnu99; 367 | GCC_DYNAMIC_NO_PIC = NO; 368 | GCC_NO_COMMON_BLOCKS = YES; 369 | GCC_OPTIMIZATION_LEVEL = 0; 370 | GCC_PREPROCESSOR_DEFINITIONS = ( 371 | "DEBUG=1", 372 | "$(inherited)", 373 | ); 374 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 375 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 376 | GCC_WARN_UNDECLARED_SELECTOR = YES; 377 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 378 | GCC_WARN_UNUSED_FUNCTION = YES; 379 | GCC_WARN_UNUSED_VARIABLE = YES; 380 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 381 | MTL_ENABLE_DEBUG_INFO = YES; 382 | ONLY_ACTIVE_ARCH = YES; 383 | SDKROOT = iphoneos; 384 | TARGETED_DEVICE_FAMILY = "1,2"; 385 | }; 386 | name = Debug; 387 | }; 388 | 97C147041CF9000F007C117D /* Release */ = { 389 | isa = XCBuildConfiguration; 390 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 391 | buildSettings = { 392 | ALWAYS_SEARCH_USER_PATHS = NO; 393 | CLANG_ANALYZER_NONNULL = YES; 394 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 395 | CLANG_CXX_LIBRARY = "libc++"; 396 | CLANG_ENABLE_MODULES = YES; 397 | CLANG_ENABLE_OBJC_ARC = YES; 398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_COMMA = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 403 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 404 | CLANG_WARN_EMPTY_BODY = YES; 405 | CLANG_WARN_ENUM_CONVERSION = YES; 406 | CLANG_WARN_INFINITE_RECURSION = YES; 407 | CLANG_WARN_INT_CONVERSION = YES; 408 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 409 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 410 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 411 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 412 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 413 | CLANG_WARN_STRICT_PROTOTYPES = YES; 414 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 415 | CLANG_WARN_UNREACHABLE_CODE = YES; 416 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 417 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 418 | COPY_PHASE_STRIP = NO; 419 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 420 | ENABLE_NS_ASSERTIONS = NO; 421 | ENABLE_STRICT_OBJC_MSGSEND = YES; 422 | GCC_C_LANGUAGE_STANDARD = gnu99; 423 | GCC_NO_COMMON_BLOCKS = YES; 424 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 425 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 426 | GCC_WARN_UNDECLARED_SELECTOR = YES; 427 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 428 | GCC_WARN_UNUSED_FUNCTION = YES; 429 | GCC_WARN_UNUSED_VARIABLE = YES; 430 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 431 | MTL_ENABLE_DEBUG_INFO = NO; 432 | SDKROOT = iphoneos; 433 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 434 | TARGETED_DEVICE_FAMILY = "1,2"; 435 | VALIDATE_PRODUCT = YES; 436 | }; 437 | name = Release; 438 | }; 439 | 97C147061CF9000F007C117D /* Debug */ = { 440 | isa = XCBuildConfiguration; 441 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 442 | buildSettings = { 443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 444 | CLANG_ENABLE_MODULES = YES; 445 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 446 | ENABLE_BITCODE = NO; 447 | FRAMEWORK_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "$(PROJECT_DIR)/Flutter", 450 | ); 451 | INFOPLIST_FILE = Runner/Info.plist; 452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 453 | LIBRARY_SEARCH_PATHS = ( 454 | "$(inherited)", 455 | "$(PROJECT_DIR)/Flutter", 456 | ); 457 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 460 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 461 | SWIFT_VERSION = 4.0; 462 | VERSIONING_SYSTEM = "apple-generic"; 463 | }; 464 | name = Debug; 465 | }; 466 | 97C147071CF9000F007C117D /* Release */ = { 467 | isa = XCBuildConfiguration; 468 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 469 | buildSettings = { 470 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 471 | CLANG_ENABLE_MODULES = YES; 472 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 473 | ENABLE_BITCODE = NO; 474 | FRAMEWORK_SEARCH_PATHS = ( 475 | "$(inherited)", 476 | "$(PROJECT_DIR)/Flutter", 477 | ); 478 | INFOPLIST_FILE = Runner/Info.plist; 479 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 480 | LIBRARY_SEARCH_PATHS = ( 481 | "$(inherited)", 482 | "$(PROJECT_DIR)/Flutter", 483 | ); 484 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 485 | PRODUCT_NAME = "$(TARGET_NAME)"; 486 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 487 | SWIFT_VERSION = 4.0; 488 | VERSIONING_SYSTEM = "apple-generic"; 489 | }; 490 | name = Release; 491 | }; 492 | /* End XCBuildConfiguration section */ 493 | 494 | /* Begin XCConfigurationList section */ 495 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | 97C147031CF9000F007C117D /* Debug */, 499 | 97C147041CF9000F007C117D /* Release */, 500 | 249021D3217E4FDB00AE95B9 /* Profile */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | 97C147061CF9000F007C117D /* Debug */, 509 | 97C147071CF9000F007C117D /* Release */, 510 | 249021D4217E4FDB00AE95B9 /* Profile */, 511 | ); 512 | defaultConfigurationIsVisible = 0; 513 | defaultConfigurationName = Release; 514 | }; 515 | /* End XCConfigurationList section */ 516 | 517 | }; 518 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 519 | } 520 | --------------------------------------------------------------------------------