├── 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.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ ├── xcshareddata
│ │ │ └── xcschemes
│ │ │ │ └── Runner.xcscheme
│ │ └── project.pbxproj
│ └── .gitignore
├── web
│ ├── favicon.png
│ ├── icons
│ │ ├── Icon-192.png
│ │ ├── Icon-512.png
│ │ ├── Icon-maskable-192.png
│ │ └── Icon-maskable-512.png
│ ├── manifest.json
│ └── index.html
├── android
│ ├── gradle.properties
│ ├── .gitignore
│ ├── 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
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ └── values
│ │ │ │ │ │ └── styles.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
├── README.md
├── .metadata
├── .gitignore
├── test
│ └── widget_test.dart
├── pubspec.yaml
├── pubspec.lock
└── lib
│ └── main.dart
├── analysis_options.yaml
├── lib
├── util
│ ├── horizontal_scrollbar.dart
│ ├── multi_select_list_type.dart
│ ├── multi_select_item.dart
│ └── multi_select_actions.dart
├── multi_select_flutter.dart
├── chip_display
│ └── multi_select_chip_display.dart
├── dialog
│ ├── mult_select_dialog.dart
│ └── multi_select_dialog_field.dart
├── bottom_sheet
│ ├── multi_select_bottom_sheet.dart
│ └── multi_select_bottom_sheet_field.dart
└── chip_field
│ └── multi_select_chip_field.dart
├── test
└── multi_select_flutter_test.dart
├── .metadata
├── LICENSE
├── .gitignore
├── pubspec.yaml
├── pubspec.lock
├── README.md
└── CHANGELOG.md
/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"
2 |
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/example/web/favicon.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | Example for multi_select_flutter package.
4 |
5 | Check out some different ways to implement multi_select_flutter.
6 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | analyzer:
2 | exclude: [build/**]
3 | strong-mode:
4 | implicit-casts: true
5 |
6 | linter:
7 | rules:
8 | - camel_case_types
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
--------------------------------------------------------------------------------
/lib/util/horizontal_scrollbar.dart:
--------------------------------------------------------------------------------
1 | class HorizontalScrollBar {
2 | final bool isAlwaysShown;
3 |
4 | HorizontalScrollBar({this.isAlwaysShown = false});
5 | }
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/lib/util/multi_select_list_type.dart:
--------------------------------------------------------------------------------
1 | /// Used by MultiSelectDialog and MultiSelectBottomSheet to determine which type of list to render.
2 | enum MultiSelectListType { LIST, CHIP }
3 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/test/multi_select_flutter_test.dart:
--------------------------------------------------------------------------------
1 | // import 'package:flutter_test/flutter_test.dart';
2 | // import 'package:flutter_multi_select/flutter_multi_select.dart';
3 |
4 | void main() {
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pt10/multi_select_flutter/master/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/pt10/multi_select_flutter/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/util/multi_select_item.dart:
--------------------------------------------------------------------------------
1 | /// A model class used to represent a selectable item.
2 | class MultiSelectItem {
3 | final T value;
4 | final String label;
5 | bool selected = false;
6 |
7 | MultiSelectItem(this.value, this.label);
8 | }
9 |
--------------------------------------------------------------------------------
/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-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.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: b041144f833e05cf463b8887fa12efdec9493488
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: b041144f833e05cf463b8887fa12efdec9493488
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/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 |
--------------------------------------------------------------------------------
/lib/multi_select_flutter.dart:
--------------------------------------------------------------------------------
1 | export 'util/multi_select_item.dart';
2 | export 'util/multi_select_list_type.dart';
3 | export 'util/multi_select_actions.dart';
4 | export 'dialog/mult_select_dialog.dart';
5 | export 'dialog/multi_select_dialog_field.dart';
6 | export 'bottom_sheet/multi_select_bottom_sheet.dart';
7 | export 'bottom_sheet/multi_select_bottom_sheet_field.dart';
8 | export 'chip_display/multi_select_chip_display.dart';
9 | export 'chip_field/multi_select_chip_field.dart';
10 | export 'util/horizontal_scrollbar.dart';
11 |
--------------------------------------------------------------------------------
/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/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | include ':app'
6 |
7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
8 | def properties = new Properties()
9 |
10 | assert localPropertiesFile.exists()
11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
12 |
13 | def flutterSdkPath = properties.getProperty("flutter.sdk")
14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
16 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.0'
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/.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 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | /build/
32 |
33 | # Web related
34 | lib/generated_plugin_registrant.dart
35 |
36 | # Symbolication related
37 | app.*.symbols
38 |
39 | # Obfuscation related
40 | app.*.map.json
41 |
42 | # Exceptions to above rules.
43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
44 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "short_name": "example",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:example/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020, Chris Botha
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4 | following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7 | disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
10 | disclaimer in the documentation and/or other materials provided with the distribution.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
13 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
14 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
15 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
16 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
17 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
18 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 |
--------------------------------------------------------------------------------
/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 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | build/
32 |
33 | # Android related
34 | **/android/**/gradle-wrapper.jar
35 | **/android/.gradle
36 | **/android/captures/
37 | **/android/gradlew
38 | **/android/gradlew.bat
39 | **/android/local.properties
40 | **/android/**/GeneratedPluginRegistrant.java
41 |
42 | # iOS/XCode related
43 | **/ios/**/*.mode1v3
44 | **/ios/**/*.mode2v3
45 | **/ios/**/*.moved-aside
46 | **/ios/**/*.pbxuser
47 | **/ios/**/*.perspectivev3
48 | **/ios/**/*sync/
49 | **/ios/**/.sconsign.dblite
50 | **/ios/**/.tags*
51 | **/ios/**/.vagrant/
52 | **/ios/**/DerivedData/
53 | **/ios/**/Icon?
54 | **/ios/**/Pods/
55 | **/ios/**/.symlinks/
56 | **/ios/**/profile
57 | **/ios/**/xcuserdata
58 | **/ios/.generated/
59 | **/ios/Flutter/App.framework
60 | **/ios/Flutter/Flutter.framework
61 | **/ios/Flutter/Flutter.podspec
62 | **/ios/Flutter/Generated.xcconfig
63 | **/ios/Flutter/app.flx
64 | **/ios/Flutter/app.zip
65 | **/ios/Flutter/flutter_assets/
66 | **/ios/Flutter/flutter_export_environment.sh
67 | **/ios/ServiceDefinitions.json
68 | **/ios/Runner/GeneratedPluginRegistrant.*
69 |
70 | # Exceptions to above rules.
71 | !**/ios/**/default.mode1v3
72 | !**/ios/**/default.mode2v3
73 | !**/ios/**/default.pbxuser
74 | !**/ios/**/default.perspectivev3
75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
76 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: multi_select_flutter
2 | description: A flexible multi select package for Flutter. Make multi select widgets the way you want.
3 | version: 4.1.3
4 | repository: https://github.com/CHB61/flutter-multi-select
5 | issue_tracker: https://github.com/CHB61/flutter-multi-select
6 | documentation: https://github.com/CHB61/flutter-multi-select
7 | homepage: https://github.com/CHB61/flutter-multi-select
8 |
9 | environment:
10 | sdk: '>=2.12.0 <3.0.0'
11 |
12 | dependencies:
13 | flutter:
14 | sdk: flutter
15 | collection: ^1.15.0
16 |
17 | dev_dependencies:
18 | flutter_test:
19 | sdk: flutter
20 |
21 | # For information on the generic Dart part of this file, see the
22 | # following page: https://dart.dev/tools/pub/pubspec
23 |
24 | # The following section is specific to Flutter.
25 | flutter:
26 |
27 | # To add assets to your package, add an assets section, like this:
28 | # assets:
29 | # - images/a_dot_burr.jpeg
30 | # - images/a_dot_ham.jpeg
31 | #
32 | # For details regarding assets in packages, see
33 | # https://flutter.dev/assets-and-images/#from-packages
34 | #
35 | # An image asset can refer to one or more resolution-specific "variants", see
36 | # https://flutter.dev/assets-and-images/#resolution-aware.
37 |
38 | # To add custom fonts to your package, add a fonts section here,
39 | # in this "flutter" section. Each entry in this list should have a
40 | # "family" key with the font family name, and a "fonts" key with a
41 | # list giving the asset and other descriptors for the font. For
42 | # example:
43 | # fonts:
44 | # - family: Schyler
45 | # fonts:
46 | # - asset: fonts/Schyler-Regular.ttf
47 | # - asset: fonts/Schyler-Italic.ttf
48 | # style: italic
49 | # - family: Trajan Pro
50 | # fonts:
51 | # - asset: fonts/TrajanPro.ttf
52 | # - asset: fonts/TrajanPro_Bold.ttf
53 | # weight: 700
54 | #
55 | # For details regarding fonts in packages, see
56 | # https://flutter.dev/custom-fonts/#from-packages
57 |
--------------------------------------------------------------------------------
/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 | }
47 |
48 | buildTypes {
49 | release {
50 | // TODO: Add your own signing config for the release build.
51 | // Signing with the debug keys for now, so `flutter run --release` works.
52 | signingConfig signingConfigs.debug
53 | }
54 | }
55 | }
56 |
57 | flutter {
58 | source '../..'
59 | }
60 |
61 | dependencies {
62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
63 | }
64 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: A new Flutter project.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev
7 |
8 | version: 1.0.0+1
9 |
10 | environment:
11 | sdk: ">=2.7.0 <3.0.0"
12 |
13 | dependencies:
14 | flutter:
15 | sdk: flutter
16 | multi_select_flutter:
17 | path: ../
18 |
19 | # The following adds the Cupertino Icons font to your application.
20 | # Use with the CupertinoIcons class for iOS style icons.
21 | cupertino_icons: ^0.1.3
22 |
23 | dev_dependencies:
24 | flutter_test:
25 | sdk: flutter
26 |
27 | # For information on the generic Dart part of this file, see the
28 | # following page: https://dart.dev/tools/pub/pubspec
29 |
30 | # The following section is specific to Flutter.
31 | flutter:
32 | # The following line ensures that the Material Icons font is
33 | # included with your application, so that you can use the icons in
34 | # the material Icons class.
35 | uses-material-design: true
36 |
37 | # To add assets to your application, add an assets section, like this:
38 | # assets:
39 | # - images/a_dot_burr.jpeg
40 | # - images/a_dot_ham.jpeg
41 |
42 | # An image asset can refer to one or more resolution-specific "variants", see
43 | # https://flutter.dev/assets-and-images/#resolution-aware.
44 |
45 | # For details regarding adding assets from package dependencies, see
46 | # https://flutter.dev/assets-and-images/#from-packages
47 |
48 | # To add custom fonts to your application, add a fonts section here,
49 | # in this "flutter" section. Each entry in this list should have a
50 | # "family" key with the font family name, and a "fonts" key with a
51 | # list giving the asset and other descriptors for the font. For
52 | # example:
53 | # fonts:
54 | # - family: Schyler
55 | # fonts:
56 | # - asset: fonts/Schyler-Regular.ttf
57 | # - asset: fonts/Schyler-Italic.ttf
58 | # style: italic
59 | # - family: Trajan Pro
60 | # fonts:
61 | # - asset: fonts/TrajanPro.ttf
62 | # - asset: fonts/TrajanPro_Bold.ttf
63 | # weight: 700
64 | #
65 | # For details regarding fonts from package dependencies,
66 | # see https://flutter.dev/custom-fonts/#from-packages
67 |
--------------------------------------------------------------------------------
/lib/util/multi_select_actions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'multi_select_item.dart';
3 |
4 | /// Contains common actions that are used by different multi select classes.
5 | class MultiSelectActions {
6 | List onItemCheckedChange(
7 | List selectedValues, T itemValue, bool checked) {
8 | if (checked) {
9 | selectedValues.add(itemValue);
10 | } else {
11 | selectedValues.remove(itemValue);
12 | }
13 | return selectedValues;
14 | }
15 |
16 | /// Pops the dialog from the navigation stack and returns the initially selected values.
17 | void onCancelTap(BuildContext ctx, List initiallySelectedValues) {
18 | Navigator.pop(ctx, initiallySelectedValues);
19 | }
20 |
21 | /// Pops the dialog from the navigation stack and returns the selected values.
22 | /// Calls the onConfirm function if one was provided.
23 | void onConfirmTap(
24 | BuildContext ctx, List selectedValues, Function(List)? onConfirm) {
25 | Navigator.pop(ctx, selectedValues);
26 | if (onConfirm != null) {
27 | onConfirm(selectedValues);
28 | }
29 | }
30 |
31 | /// Accepts the search query, and the original list of items.
32 | /// If the search query is valid, return a filtered list, otherwise return the original list.
33 | List> updateSearchQuery(
34 | String? val, List> allItems) {
35 | if (val != null && val.trim().isNotEmpty) {
36 | List> filteredItems = [];
37 | for (var item in allItems) {
38 | if (item.label.toLowerCase().contains(val.toLowerCase())) {
39 | filteredItems.add(item);
40 | }
41 | }
42 | return filteredItems;
43 | } else {
44 | return allItems;
45 | }
46 | }
47 |
48 | /// Toggles the search field.
49 | bool onSearchTap(bool showSearch) {
50 | return !showSearch;
51 | }
52 |
53 | List> separateSelected(List> list) {
54 | List> _selectedItems = [];
55 | List> _nonSelectedItems = [];
56 |
57 | _nonSelectedItems.addAll(list.where((element) => !element.selected));
58 | _nonSelectedItems.sort((a, b) => a.label.compareTo(b.label));
59 | _selectedItems.addAll(list.where((element) => element.selected));
60 | _selectedItems.sort((a, b) => a.label.compareTo(b.label));
61 |
62 | return [..._selectedItems, ..._nonSelectedItems];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/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/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
12 |
19 |
23 |
27 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.9.0"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "1.2.1"
25 | clock:
26 | dependency: transitive
27 | description:
28 | name: clock
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.1.1"
32 | collection:
33 | dependency: "direct main"
34 | description:
35 | name: collection
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.16.0"
39 | fake_async:
40 | dependency: transitive
41 | description:
42 | name: fake_async
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.3.1"
46 | flutter:
47 | dependency: "direct main"
48 | description: flutter
49 | source: sdk
50 | version: "0.0.0"
51 | flutter_test:
52 | dependency: "direct dev"
53 | description: flutter
54 | source: sdk
55 | version: "0.0.0"
56 | matcher:
57 | dependency: transitive
58 | description:
59 | name: matcher
60 | url: "https://pub.dartlang.org"
61 | source: hosted
62 | version: "0.12.12"
63 | material_color_utilities:
64 | dependency: transitive
65 | description:
66 | name: material_color_utilities
67 | url: "https://pub.dartlang.org"
68 | source: hosted
69 | version: "0.1.5"
70 | meta:
71 | dependency: transitive
72 | description:
73 | name: meta
74 | url: "https://pub.dartlang.org"
75 | source: hosted
76 | version: "1.8.0"
77 | path:
78 | dependency: transitive
79 | description:
80 | name: path
81 | url: "https://pub.dartlang.org"
82 | source: hosted
83 | version: "1.8.2"
84 | sky_engine:
85 | dependency: transitive
86 | description: flutter
87 | source: sdk
88 | version: "0.0.99"
89 | source_span:
90 | dependency: transitive
91 | description:
92 | name: source_span
93 | url: "https://pub.dartlang.org"
94 | source: hosted
95 | version: "1.9.0"
96 | stack_trace:
97 | dependency: transitive
98 | description:
99 | name: stack_trace
100 | url: "https://pub.dartlang.org"
101 | source: hosted
102 | version: "1.10.0"
103 | stream_channel:
104 | dependency: transitive
105 | description:
106 | name: stream_channel
107 | url: "https://pub.dartlang.org"
108 | source: hosted
109 | version: "2.1.0"
110 | string_scanner:
111 | dependency: transitive
112 | description:
113 | name: string_scanner
114 | url: "https://pub.dartlang.org"
115 | source: hosted
116 | version: "1.1.1"
117 | term_glyph:
118 | dependency: transitive
119 | description:
120 | name: term_glyph
121 | url: "https://pub.dartlang.org"
122 | source: hosted
123 | version: "1.2.1"
124 | test_api:
125 | dependency: transitive
126 | description:
127 | name: test_api
128 | url: "https://pub.dartlang.org"
129 | source: hosted
130 | version: "0.4.12"
131 | vector_math:
132 | dependency: transitive
133 | description:
134 | name: vector_math
135 | url: "https://pub.dartlang.org"
136 | source: hosted
137 | version: "2.1.2"
138 | sdks:
139 | dart: ">=2.17.0-0 <3.0.0"
140 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.9.0"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "1.2.1"
25 | clock:
26 | dependency: transitive
27 | description:
28 | name: clock
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.1.1"
32 | collection:
33 | dependency: transitive
34 | description:
35 | name: collection
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.16.0"
39 | cupertino_icons:
40 | dependency: "direct main"
41 | description:
42 | name: cupertino_icons
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "0.1.3"
46 | fake_async:
47 | dependency: transitive
48 | description:
49 | name: fake_async
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "1.3.1"
53 | flutter:
54 | dependency: "direct main"
55 | description: flutter
56 | source: sdk
57 | version: "0.0.0"
58 | flutter_test:
59 | dependency: "direct dev"
60 | description: flutter
61 | source: sdk
62 | version: "0.0.0"
63 | matcher:
64 | dependency: transitive
65 | description:
66 | name: matcher
67 | url: "https://pub.dartlang.org"
68 | source: hosted
69 | version: "0.12.12"
70 | material_color_utilities:
71 | dependency: transitive
72 | description:
73 | name: material_color_utilities
74 | url: "https://pub.dartlang.org"
75 | source: hosted
76 | version: "0.1.5"
77 | meta:
78 | dependency: transitive
79 | description:
80 | name: meta
81 | url: "https://pub.dartlang.org"
82 | source: hosted
83 | version: "1.8.0"
84 | multi_select_flutter:
85 | dependency: "direct main"
86 | description:
87 | path: ".."
88 | relative: true
89 | source: path
90 | version: "4.1.3"
91 | path:
92 | dependency: transitive
93 | description:
94 | name: path
95 | url: "https://pub.dartlang.org"
96 | source: hosted
97 | version: "1.8.2"
98 | sky_engine:
99 | dependency: transitive
100 | description: flutter
101 | source: sdk
102 | version: "0.0.99"
103 | source_span:
104 | dependency: transitive
105 | description:
106 | name: source_span
107 | url: "https://pub.dartlang.org"
108 | source: hosted
109 | version: "1.9.0"
110 | stack_trace:
111 | dependency: transitive
112 | description:
113 | name: stack_trace
114 | url: "https://pub.dartlang.org"
115 | source: hosted
116 | version: "1.10.0"
117 | stream_channel:
118 | dependency: transitive
119 | description:
120 | name: stream_channel
121 | url: "https://pub.dartlang.org"
122 | source: hosted
123 | version: "2.1.0"
124 | string_scanner:
125 | dependency: transitive
126 | description:
127 | name: string_scanner
128 | url: "https://pub.dartlang.org"
129 | source: hosted
130 | version: "1.1.1"
131 | term_glyph:
132 | dependency: transitive
133 | description:
134 | name: term_glyph
135 | url: "https://pub.dartlang.org"
136 | source: hosted
137 | version: "1.2.1"
138 | test_api:
139 | dependency: transitive
140 | description:
141 | name: test_api
142 | url: "https://pub.dartlang.org"
143 | source: hosted
144 | version: "0.4.12"
145 | vector_math:
146 | dependency: transitive
147 | description:
148 | name: vector_math
149 | url: "https://pub.dartlang.org"
150 | source: hosted
151 | version: "2.1.2"
152 | sdks:
153 | dart: ">=2.17.0-0 <3.0.0"
154 |
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | example
33 |
34 |
35 |
36 |
39 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/lib/chip_display/multi_select_chip_display.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../util/horizontal_scrollbar.dart';
3 | import '../util/multi_select_item.dart';
4 |
5 | /// A widget meant to display selected values as chips.
6 | // ignore: must_be_immutable
7 | class MultiSelectChipDisplay extends StatelessWidget {
8 | /// The source list of selected items.
9 | final List?>? items;
10 |
11 | /// Fires when a chip is tapped.
12 | final Function(V)? onTap;
13 |
14 | /// Set the chip color.
15 | final Color? chipColor;
16 |
17 | /// Change the alignment of the chips.
18 | final Alignment? alignment;
19 |
20 | /// Style the Container that makes up the chip display.
21 | final BoxDecoration? decoration;
22 |
23 | /// Style the text on the chips.
24 | final TextStyle? textStyle;
25 |
26 | /// A function that sets the color of selected items based on their value.
27 | final Color? Function(V)? colorator;
28 |
29 | /// An icon to display prior to the chip's label.
30 | final Icon? icon;
31 |
32 | /// Set a ShapeBorder. Typically a RoundedRectangularBorder.
33 | final ShapeBorder? shape;
34 |
35 | /// Enables horizontal scrolling.
36 | final bool scroll;
37 |
38 | /// Enables the scrollbar when scroll is `true`.
39 | final HorizontalScrollBar? scrollBar;
40 |
41 | final ScrollController _scrollController = ScrollController();
42 |
43 | /// Set a fixed height.
44 | final double? height;
45 |
46 | /// Set the width of the chips.
47 | final double? chipWidth;
48 |
49 | bool? disabled;
50 |
51 | MultiSelectChipDisplay({
52 | this.items,
53 | this.onTap,
54 | this.chipColor,
55 | this.alignment,
56 | this.decoration,
57 | this.textStyle,
58 | this.colorator,
59 | this.icon,
60 | this.shape,
61 | this.scroll = false,
62 | this.scrollBar,
63 | this.height,
64 | this.chipWidth,
65 | }) {
66 | this.disabled = false;
67 | }
68 |
69 | MultiSelectChipDisplay.none({
70 | this.items = const [],
71 | this.disabled = true,
72 | this.onTap,
73 | this.chipColor,
74 | this.alignment,
75 | this.decoration,
76 | this.textStyle,
77 | this.colorator,
78 | this.icon,
79 | this.shape,
80 | this.scroll = false,
81 | this.scrollBar,
82 | this.height,
83 | this.chipWidth,
84 | });
85 |
86 | @override
87 | Widget build(BuildContext context) {
88 | if (items == null || items!.isEmpty) return Container();
89 | return Container(
90 | decoration: decoration,
91 | alignment: alignment ?? Alignment.centerLeft,
92 | padding: EdgeInsets.symmetric(horizontal: scroll ? 0 : 10),
93 | child: scroll
94 | ? Container(
95 | width: MediaQuery.of(context).size.width,
96 | height: height ?? MediaQuery.of(context).size.height * 0.08,
97 | child: scrollBar != null
98 | ? Scrollbar(
99 | thumbVisibility: scrollBar!.isAlwaysShown,
100 | controller: _scrollController,
101 | child: ListView.builder(
102 | controller: _scrollController,
103 | scrollDirection: Axis.horizontal,
104 | itemCount: items!.length,
105 | itemBuilder: (ctx, index) {
106 | return _buildItem(items![index]!, context);
107 | },
108 | ),
109 | )
110 | : ListView.builder(
111 | controller: _scrollController,
112 | scrollDirection: Axis.horizontal,
113 | itemCount: items!.length,
114 | itemBuilder: (ctx, index) {
115 | return _buildItem(items![index]!, context);
116 | },
117 | ),
118 | )
119 | : Wrap(
120 | children: items != null
121 | ? items!.map((item) => _buildItem(item!, context)).toList()
122 | : [
123 | Container(),
124 | ],
125 | ),
126 | );
127 | }
128 |
129 | Widget _buildItem(MultiSelectItem item, BuildContext context) {
130 | return Container(
131 | padding: const EdgeInsets.all(2.0),
132 | child: ChoiceChip(
133 | shape: shape as OutlinedBorder?,
134 | avatar: icon != null
135 | ? Icon(
136 | icon!.icon,
137 | color: colorator != null && colorator!(item.value) != null
138 | ? colorator!(item.value)!.withOpacity(1)
139 | : icon!.color ?? Theme.of(context).primaryColor,
140 | )
141 | : null,
142 | label: Container(
143 | width: chipWidth,
144 | child: Text(
145 | item.label,
146 | overflow: TextOverflow.ellipsis,
147 | style: TextStyle(
148 | color: colorator != null && colorator!(item.value) != null
149 | ? textStyle != null
150 | ? textStyle!.color ?? colorator!(item.value)
151 | : colorator!(item.value)
152 | : textStyle != null && textStyle!.color != null
153 | ? textStyle!.color
154 | : chipColor != null
155 | ? chipColor!.withOpacity(1)
156 | : null,
157 | fontSize: textStyle != null ? textStyle!.fontSize : null,
158 | ),
159 | ),
160 | ),
161 | selected: items!.contains(item),
162 | selectedColor: colorator != null && colorator!(item.value) != null
163 | ? colorator!(item.value)
164 | : chipColor != null
165 | ? chipColor
166 | : Theme.of(context).primaryColor.withOpacity(0.33),
167 | onSelected: (_) {
168 | if (onTap != null) onTap!(item.value);
169 | },
170 | ),
171 | );
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Multi Select Flutter
3 |
4 | [](https://pub.dev/packages/multi_select_flutter)
5 |
6 | Multi Select Flutter is a package for creating multi-select widgets in a variety of ways.
7 |
8 | | 
Dialog | 
BottomSheet | 
ChoiceChip |
9 | | :---: | :---: | :---: |
10 |
11 | ## Features
12 | - Supports FormField features like validator.
13 | - Neutral default design.
14 | - Dialog, BottomSheet, or ChoiceChip style widgets.
15 | - Make your multi select `searchable` for larger lists.
16 |
17 | ## Usage
18 |
19 | ### MultiSelectDialogField / MultiSelectBottomSheetField
20 | 
21 |
22 | These widgets provide an InkWell button which open the dialog or bottom sheet and are equipped with FormField features. You can customize it using the provided parameters.
23 |
24 | To store the selected values, you can use the `onConfirm` parameter. You could also use `onSelectionChanged` for this.
25 |
26 | By default these widgets render a MultiSelectChipDisplay below the field. This can be overridden with the `chipDisplay` parameter or removed completely by using `chipDisplay: MultiSelectChipDisplay.none()`.
27 |
28 | ```dart
29 | MultiSelectDialogField(
30 | items: _animals.map((e) => MultiSelectItem(e, e.name)).toList(),
31 | listType: MultiSelectListType.CHIP,
32 | onConfirm: (values) {
33 | _selectedAnimals = values;
34 | },
35 | ),
36 | ```
37 |
38 | ### MultiSelectDialog / MultiSelectBottomSheet
39 |
40 |
41 | If you prefer to create your own button for opening the dialog or bottom sheet, you may do so and then make a call to a function like this:
42 |
43 | `MultiSelectDialog` can be used in the builder of `showDialog()`.
44 | ```dart
45 | void _showMultiSelect(BuildContext context) async {
46 | await showDialog(
47 | context: context,
48 | builder: (ctx) {
49 | return MultiSelectDialog(
50 | items: _items,
51 | initialValue: _selectedAnimals,
52 | onConfirm: (values) {...},
53 | );
54 | },
55 | );
56 | }
57 | ```
58 |
59 | `MultiSelectBottomSheet` can be used in the builder of `showModalBottomSheet()`.
60 | ```dart
61 | void _showMultiSelect(BuildContext context) async {
62 | await showModalBottomSheet(
63 | isScrollControlled: true, // required for min/max child size
64 | context: context,
65 | builder: (ctx) {
66 | return MultiSelectBottomSheet(
67 | items: _items,
68 | initialValue: _selectedAnimals,
69 | onConfirm: (values) {...},
70 | maxChildSize: 0.8,
71 | );
72 | },
73 | );
74 | }
75 | ```
76 |
77 | ### MultiSelectChipDisplay
78 |
79 |
80 | To display the selected items, this widget can be used alongside your own button or it can be specified as a `chipDisplay` parameter of widgets like `MultiSelectDialogField`.
81 |
82 | You can also remove items from the source list in the onTap function.
83 |
84 | ```dart
85 | MultiSelectChipDisplay(
86 | items: _selectedAnimals.map((e) => MultiSelectItem(e, e)).toList(),
87 | onTap: (value) {
88 | setState(() {
89 | _selectedAnimals.remove(value);
90 | });
91 | },
92 | ),
93 | ```
94 | A MultiSelectChipDisplay that is part of a MultiSelectDialogField still renders outside the BoxDecoration of the MultiSelectDialogField as seen here:
95 |
96 |
97 |
98 | If you want to encapsulate the MultiSelectChipDisplay, wrap the MultiSelectDialogField in a Container and apply the decoration to that instead:
99 |
100 | ```dart
101 | Container(
102 | decoration: BoxDecoration(...),
103 | child: MultiSelectDialogField(
104 | items: _items,
105 | chipDisplay: MultiSelectChipDisplay(...),
106 | ),
107 | ),
108 | ```
109 |
110 |
111 | ### MultiSelectChipField
112 |
113 |
114 | This widget is similar to MultiSelectChipDisplay, except these chips are the primary interface for selecting items.
115 | ```dart
116 | MultiSelectChipField(
117 | items: _items,
118 | icon: Icon(Icons.check),
119 | onTap: (values) {
120 | _selectedAnimals = values;
121 | },
122 | ),
123 | ```
124 | #### Using `itemBuilder` to create custom items:
125 | ```dart
126 | MultiSelectChipField(
127 | items: _items,
128 | key: _multiSelectKey,
129 | validator: (values) {...}
130 | itemBuilder: (item, state) {
131 | // return your custom widget here
132 | return InkWell(
133 | onTap: () {
134 | _selectedAnimals.contains(item.value)
135 | ? _selectedAnimals.remove(item.value)
136 | : _selectedAnimals.add(item.value);
137 | state.didChange(_selectedAnimals);
138 | _multiSelectKey.currentState.validate();
139 | },
140 | child: Text(item.value.name),
141 | );
142 | },
143 | ),
144 | ```
145 | The `itemBuilder` param takes a function that will create a widget for each of the provided `items`.
146 |
147 | In order to use validator and other FormField features with custom widgets, you must call `state.didChange(_updatedList)` any time the list of selected items is updated.
148 |
149 | #### Using `scrollControl` to auto scroll:
150 | ```dart
151 | MultiSelectChipField(
152 | items: _items,
153 | scrollControl: (controller) {
154 | _startAnimation(controller);
155 | },
156 | )
157 |
158 | // waits 5 seconds, scrolls to end slow, then back fast
159 | void _startAnimation(ScrollController controller) {
160 | // when using more than one animation, use async/await
161 | Future.delayed(const Duration(milliseconds: 5000), () async {
162 | await controller.animateTo(
163 | controller.position.maxScrollExtent,
164 | duration: Duration(milliseconds: 8000),
165 | curve: Curves.linear);
166 |
167 | await controller.animateTo(
168 | controller.position.minScrollExtent,
169 | duration: Duration(milliseconds: 1250),
170 | curve: Curves.fastLinearToSlowEaseIn);
171 | });
172 | }
173 | ```
174 |
175 | ## Contributing
176 |
177 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:multi_select_flutter/multi_select_flutter.dart';
3 |
4 | void main() {
5 | runApp(MyApp());
6 | }
7 |
8 | class MyApp extends StatelessWidget {
9 | @override
10 | Widget build(BuildContext context) {
11 | return MaterialApp(
12 | title: 'Flutter Multi Select',
13 | theme: ThemeData(
14 | primarySwatch: Colors.purple,
15 | visualDensity: VisualDensity.adaptivePlatformDensity,
16 | ),
17 | home: MyHomePage(title: 'Flutter Multi Select'),
18 | );
19 | }
20 | }
21 |
22 | class Animal {
23 | final int id;
24 | final String name;
25 |
26 | Animal({
27 | this.id,
28 | this.name,
29 | });
30 | }
31 |
32 | class MyHomePage extends StatefulWidget {
33 | MyHomePage({Key key, this.title}) : super(key: key);
34 | final String title;
35 | @override
36 | _MyHomePageState createState() => _MyHomePageState();
37 | }
38 |
39 | class _MyHomePageState extends State {
40 | static List _animals = [
41 | Animal(id: 1, name: "Lion"),
42 | Animal(id: 2, name: "Flamingo"),
43 | Animal(id: 3, name: "Hippo"),
44 | Animal(id: 4, name: "Horse"),
45 | Animal(id: 5, name: "Tiger"),
46 | Animal(id: 6, name: "Penguin"),
47 | Animal(id: 7, name: "Spider"),
48 | Animal(id: 8, name: "Snake"),
49 | Animal(id: 9, name: "Bear"),
50 | Animal(id: 10, name: "Beaver"),
51 | Animal(id: 11, name: "Cat"),
52 | Animal(id: 12, name: "Fish"),
53 | Animal(id: 13, name: "Rabbit"),
54 | Animal(id: 14, name: "Mouse"),
55 | Animal(id: 15, name: "Dog"),
56 | Animal(id: 16, name: "Zebra"),
57 | Animal(id: 17, name: "Cow"),
58 | Animal(id: 18, name: "Frog"),
59 | Animal(id: 19, name: "Blue Jay"),
60 | Animal(id: 20, name: "Moose"),
61 | Animal(id: 21, name: "Gecko"),
62 | Animal(id: 22, name: "Kangaroo"),
63 | Animal(id: 23, name: "Shark"),
64 | Animal(id: 24, name: "Crocodile"),
65 | Animal(id: 25, name: "Owl"),
66 | Animal(id: 26, name: "Dragonfly"),
67 | Animal(id: 27, name: "Dolphin"),
68 | ];
69 | final _items = _animals
70 | .map((animal) => MultiSelectItem(animal, animal.name))
71 | .toList();
72 | //List _selectedAnimals = [];
73 | List _selectedAnimals2 = [];
74 | List _selectedAnimals3 = [];
75 | //List _selectedAnimals4 = [];
76 | List _selectedAnimals5 = [];
77 | final _multiSelectKey = GlobalKey();
78 |
79 | @override
80 | void initState() {
81 | _selectedAnimals5 = _animals;
82 | super.initState();
83 | }
84 |
85 | @override
86 | Widget build(BuildContext context) {
87 | return Scaffold(
88 | appBar: AppBar(
89 | title: Text(widget.title),
90 | ),
91 | body: SingleChildScrollView(
92 | child: Container(
93 | alignment: Alignment.center,
94 | padding: EdgeInsets.all(20),
95 | child: Column(
96 | children: [
97 | SizedBox(height: 40),
98 | //################################################################################################
99 | // Rounded blue MultiSelectDialogField
100 | //################################################################################################
101 | MultiSelectDialogField(
102 | items: _items,
103 | title: Text("Animals"),
104 | selectedColor: Colors.blue,
105 | decoration: BoxDecoration(
106 | color: Colors.blue.withOpacity(0.1),
107 | borderRadius: BorderRadius.all(Radius.circular(40)),
108 | border: Border.all(
109 | color: Colors.blue,
110 | width: 2,
111 | ),
112 | ),
113 | buttonIcon: Icon(
114 | Icons.pets,
115 | color: Colors.blue,
116 | ),
117 | buttonText: Text(
118 | "Favorite Animals",
119 | style: TextStyle(
120 | color: Colors.blue[800],
121 | fontSize: 16,
122 | ),
123 | ),
124 | onConfirm: (results) {
125 | //_selectedAnimals = results;
126 | },
127 | ),
128 | SizedBox(height: 50),
129 | //################################################################################################
130 | // This MultiSelectBottomSheetField has no decoration, but is instead wrapped in a Container that has
131 | // decoration applied. This allows the ChipDisplay to render inside the same Container.
132 | //################################################################################################
133 | Container(
134 | decoration: BoxDecoration(
135 | color: Theme.of(context).primaryColor.withOpacity(.4),
136 | border: Border.all(
137 | color: Theme.of(context).primaryColor,
138 | width: 2,
139 | ),
140 | ),
141 | child: Column(
142 | children: [
143 | MultiSelectBottomSheetField(
144 | initialChildSize: 0.4,
145 | listType: MultiSelectListType.CHIP,
146 | searchable: true,
147 | buttonText: Text("Favorite Animals"),
148 | title: Text("Animals"),
149 | items: _items,
150 | onConfirm: (values) {
151 | _selectedAnimals2 = values;
152 | },
153 | chipDisplay: MultiSelectChipDisplay(
154 | onTap: (value) {
155 | setState(() {
156 | _selectedAnimals2.remove(value);
157 | });
158 | },
159 | ),
160 | ),
161 | _selectedAnimals2 == null || _selectedAnimals2.isEmpty
162 | ? Container(
163 | padding: EdgeInsets.all(10),
164 | alignment: Alignment.centerLeft,
165 | child: Text(
166 | "None selected",
167 | style: TextStyle(color: Colors.black54),
168 | ))
169 | : Container(),
170 | ],
171 | ),
172 | ),
173 | SizedBox(height: 40),
174 | //################################################################################################
175 | // MultiSelectBottomSheetField with validators
176 | //################################################################################################
177 | MultiSelectBottomSheetField(
178 | key: _multiSelectKey,
179 | initialChildSize: 0.7,
180 | maxChildSize: 0.95,
181 | title: Text("Animals"),
182 | buttonText: Text("Favorite Animals"),
183 | items: _items,
184 | searchable: true,
185 | validator: (values) {
186 | if (values == null || values.isEmpty) {
187 | return "Required";
188 | }
189 | List names = values.map((e) => e.name).toList();
190 | if (names.contains("Frog")) {
191 | return "Frogs are weird!";
192 | }
193 | return null;
194 | },
195 | onConfirm: (values) {
196 | setState(() {
197 | _selectedAnimals3 = values;
198 | });
199 | _multiSelectKey.currentState.validate();
200 | },
201 | chipDisplay: MultiSelectChipDisplay(
202 | onTap: (item) {
203 | setState(() {
204 | _selectedAnimals3.remove(item);
205 | });
206 | _multiSelectKey.currentState.validate();
207 | },
208 | ),
209 | ),
210 | SizedBox(height: 40),
211 | //################################################################################################
212 | // MultiSelectChipField
213 | //################################################################################################
214 | MultiSelectChipField(
215 | items: _items,
216 | initialValue: [_animals[4], _animals[7], _animals[9]],
217 | title: Text("Animals"),
218 | headerColor: Colors.blue.withOpacity(0.5),
219 | decoration: BoxDecoration(
220 | border: Border.all(color: Colors.blue[700], width: 1.8),
221 | ),
222 | selectedChipColor: Colors.blue.withOpacity(0.5),
223 | selectedTextStyle: TextStyle(color: Colors.blue[800]),
224 | onTap: (values) {
225 | //_selectedAnimals4 = values;
226 | },
227 | ),
228 | SizedBox(height: 40),
229 | //################################################################################################
230 | // MultiSelectDialogField with initial values
231 | //################################################################################################
232 | MultiSelectDialogField(
233 | onConfirm: (val) {
234 | _selectedAnimals5 = val;
235 | },
236 | dialogWidth: MediaQuery.of(context).size.width * 0.7,
237 | items: _items,
238 | initialValue:
239 | _selectedAnimals5, // setting the value of this in initState() to pre-select values.
240 | ),
241 | ],
242 | ),
243 | ),
244 | ),
245 | );
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/lib/dialog/mult_select_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../util/multi_select_actions.dart';
3 | import '../util/multi_select_item.dart';
4 | import '../util/multi_select_list_type.dart';
5 |
6 | /// A dialog containing either a classic checkbox style list, or a chip style list.
7 | class MultiSelectDialog extends StatefulWidget with MultiSelectActions {
8 | /// List of items to select from.
9 | final List> items;
10 |
11 | /// The list of selected values before interaction.
12 | final List initialValue;
13 |
14 | /// The text at the top of the dialog.
15 | final Widget? title;
16 |
17 | /// Fires when the an item is selected / unselected.
18 | final void Function(List)? onSelectionChanged;
19 |
20 | /// Fires when confirm is tapped.
21 | final void Function(List)? onConfirm;
22 |
23 | /// Toggles search functionality. Default is false.
24 | final bool searchable;
25 |
26 | /// Text on the confirm button.
27 | final Text? confirmText;
28 |
29 | /// Text on the cancel button.
30 | final Text? cancelText;
31 |
32 | /// An enum that determines which type of list to render.
33 | final MultiSelectListType? listType;
34 |
35 | /// Sets the color of the checkbox or chip when it's selected.
36 | final Color? selectedColor;
37 |
38 | /// Sets a fixed height on the dialog.
39 | final double? height;
40 |
41 | /// Sets a fixed width on the dialog.
42 | final double? width;
43 |
44 | /// Set the placeholder text of the search field.
45 | final String? searchHint;
46 |
47 | /// A function that sets the color of selected items based on their value.
48 | /// It will either set the chip color, or the checkbox color depending on the list type.
49 | final Color? Function(T)? colorator;
50 |
51 | /// The background color of the dialog.
52 | final Color? backgroundColor;
53 |
54 | /// The color of the chip body or checkbox border while not selected.
55 | final Color? unselectedColor;
56 |
57 | /// Icon button that shows the search field.
58 | final Icon? searchIcon;
59 |
60 | /// Icon button that hides the search field
61 | final Icon? closeSearchIcon;
62 |
63 | /// Style the text on the chips or list tiles.
64 | final TextStyle? itemsTextStyle;
65 |
66 | /// Style the text on the selected chips or list tiles.
67 | final TextStyle? selectedItemsTextStyle;
68 |
69 | /// Style the search text.
70 | final TextStyle? searchTextStyle;
71 |
72 | /// Style the search hint.
73 | final TextStyle? searchHintStyle;
74 |
75 | /// Moves the selected items to the top of the list.
76 | final bool separateSelectedItems;
77 |
78 | /// Set the color of the check in the checkbox
79 | final Color? checkColor;
80 |
81 | MultiSelectDialog({
82 | required this.items,
83 | required this.initialValue,
84 | this.title,
85 | this.onSelectionChanged,
86 | this.onConfirm,
87 | this.listType,
88 | this.searchable = false,
89 | this.confirmText,
90 | this.cancelText,
91 | this.selectedColor,
92 | this.searchHint,
93 | this.height,
94 | this.width,
95 | this.colorator,
96 | this.backgroundColor,
97 | this.unselectedColor,
98 | this.searchIcon,
99 | this.closeSearchIcon,
100 | this.itemsTextStyle,
101 | this.searchHintStyle,
102 | this.searchTextStyle,
103 | this.selectedItemsTextStyle,
104 | this.separateSelectedItems = false,
105 | this.checkColor,
106 | });
107 |
108 | @override
109 | State createState() => _MultiSelectDialogState(items);
110 | }
111 |
112 | class _MultiSelectDialogState extends State> {
113 | List _selectedValues = [];
114 | bool _showSearch = false;
115 | List> _items;
116 |
117 | _MultiSelectDialogState(this._items);
118 |
119 | @override
120 | void initState() {
121 | super.initState();
122 | _selectedValues.addAll(widget.initialValue);
123 |
124 | for (int i = 0; i < _items.length; i++) {
125 | _items[i].selected = false;
126 | if (_selectedValues.contains(_items[i].value)) {
127 | _items[i].selected = true;
128 | }
129 | }
130 |
131 | if (widget.separateSelectedItems) {
132 | _items = widget.separateSelected(_items);
133 | }
134 | }
135 |
136 | /// Returns a CheckboxListTile
137 | Widget _buildListItem(MultiSelectItem item) {
138 | return Theme(
139 | data: ThemeData(
140 | unselectedWidgetColor: widget.unselectedColor ?? Colors.black54,
141 | ),
142 | child: CheckboxListTile(
143 | checkColor: widget.checkColor,
144 | value: item.selected,
145 | activeColor: widget.colorator != null
146 | ? widget.colorator!(item.value) ?? widget.selectedColor
147 | : widget.selectedColor,
148 | title: Text(
149 | item.label,
150 | style: item.selected
151 | ? widget.selectedItemsTextStyle
152 | : widget.itemsTextStyle,
153 | ),
154 | controlAffinity: ListTileControlAffinity.leading,
155 | onChanged: (checked) {
156 | setState(() {
157 | _selectedValues = widget.onItemCheckedChange(
158 | _selectedValues, item.value, checked!);
159 |
160 | if (checked) {
161 | item.selected = true;
162 | } else {
163 | item.selected = false;
164 | }
165 | if (widget.separateSelectedItems) {
166 | _items = widget.separateSelected(_items);
167 | }
168 | });
169 | if (widget.onSelectionChanged != null) {
170 | widget.onSelectionChanged!(_selectedValues);
171 | }
172 | },
173 | ),
174 | );
175 | }
176 |
177 | /// Returns a ChoiceChip
178 | Widget _buildChipItem(MultiSelectItem item) {
179 | return Container(
180 | padding: const EdgeInsets.all(2.0),
181 | child: ChoiceChip(
182 | backgroundColor: widget.unselectedColor,
183 | selectedColor: widget.colorator?.call(item.value) ??
184 | widget.selectedColor ??
185 | Theme.of(context).primaryColor.withOpacity(0.35),
186 | label: Text(
187 | item.label,
188 | style: item.selected
189 | ? TextStyle(
190 | color: widget.selectedItemsTextStyle?.color ??
191 | widget.colorator?.call(item.value) ??
192 | widget.selectedColor?.withOpacity(1) ??
193 | Theme.of(context).primaryColor,
194 | fontSize: widget.selectedItemsTextStyle?.fontSize,
195 | )
196 | : widget.itemsTextStyle,
197 | ),
198 | selected: item.selected,
199 | onSelected: (checked) {
200 | if (checked) {
201 | item.selected = true;
202 | } else {
203 | item.selected = false;
204 | }
205 | setState(() {
206 | _selectedValues = widget.onItemCheckedChange(
207 | _selectedValues, item.value, checked);
208 | });
209 | if (widget.onSelectionChanged != null) {
210 | widget.onSelectionChanged!(_selectedValues);
211 | }
212 | },
213 | ),
214 | );
215 | }
216 |
217 | @override
218 | Widget build(BuildContext context) {
219 | return AlertDialog(
220 | backgroundColor: widget.backgroundColor,
221 | title: widget.searchable == false
222 | ? widget.title ?? const Text("Select")
223 | : Row(
224 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
225 | children: [
226 | _showSearch
227 | ? Expanded(
228 | child: Container(
229 | padding: EdgeInsets.only(left: 10),
230 | child: TextField(
231 | style: widget.searchTextStyle,
232 | decoration: InputDecoration(
233 | hintStyle: widget.searchHintStyle,
234 | hintText: widget.searchHint ?? "Search",
235 | focusedBorder: UnderlineInputBorder(
236 | borderSide: BorderSide(
237 | color: widget.selectedColor ??
238 | Theme.of(context).primaryColor,
239 | ),
240 | ),
241 | ),
242 | onChanged: (val) {
243 | List> filteredList = [];
244 | filteredList =
245 | widget.updateSearchQuery(val, widget.items);
246 | setState(() {
247 | if (widget.separateSelectedItems) {
248 | _items =
249 | widget.separateSelected(filteredList);
250 | } else {
251 | _items = filteredList;
252 | }
253 | });
254 | },
255 | ),
256 | ),
257 | )
258 | : widget.title ?? Text("Select"),
259 | IconButton(
260 | icon: _showSearch
261 | ? widget.closeSearchIcon ?? Icon(Icons.close)
262 | : widget.searchIcon ?? Icon(Icons.search),
263 | onPressed: () {
264 | setState(() {
265 | _showSearch = !_showSearch;
266 | if (!_showSearch) {
267 | if (widget.separateSelectedItems) {
268 | _items = widget.separateSelected(widget.items);
269 | } else {
270 | _items = widget.items;
271 | }
272 | }
273 | });
274 | },
275 | ),
276 | ],
277 | ),
278 | contentPadding:
279 | widget.listType == null || widget.listType == MultiSelectListType.LIST
280 | ? EdgeInsets.only(top: 12.0)
281 | : EdgeInsets.all(20),
282 | content: Container(
283 | height: widget.height,
284 | width: widget.width ?? MediaQuery.of(context).size.width * 0.73,
285 | child: widget.listType == null ||
286 | widget.listType == MultiSelectListType.LIST
287 | ? ListView.builder(
288 | itemCount: _items.length,
289 | itemBuilder: (context, index) {
290 | return _buildListItem(_items[index]);
291 | },
292 | )
293 | : SingleChildScrollView(
294 | child: Wrap(
295 | children: _items.map(_buildChipItem).toList(),
296 | ),
297 | ),
298 | ),
299 | actions: [
300 | TextButton(
301 | child: widget.cancelText ??
302 | Text(
303 | "CANCEL",
304 | style: TextStyle(
305 | color: (widget.selectedColor != null &&
306 | widget.selectedColor != Colors.transparent)
307 | ? widget.selectedColor!.withOpacity(1)
308 | : Theme.of(context).primaryColor,
309 | ),
310 | ),
311 | onPressed: () {
312 | widget.onCancelTap(context, widget.initialValue);
313 | },
314 | ),
315 | TextButton(
316 | child: widget.confirmText ??
317 | Text(
318 | 'OK',
319 | style: TextStyle(
320 | color: (widget.selectedColor != null &&
321 | widget.selectedColor != Colors.transparent)
322 | ? widget.selectedColor!.withOpacity(1)
323 | : Theme.of(context).primaryColor,
324 | ),
325 | ),
326 | onPressed: () {
327 | widget.onConfirmTap(context, _selectedValues, widget.onConfirm);
328 | },
329 | )
330 | ],
331 | );
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # Changelog
3 |
4 | All notable changes to this project will be documented in this file.
5 |
6 | ## [4.1.3] - 2022-11-27
7 | ### Changed
8 | - make initialValue non-nullable
9 | - add didUpdateWidget that sets the selected items if initialValue changed
10 | - add isDismissable parameter
11 | - fixed 'items still selected after cancel'
12 |
13 | ## [4.1.2] - 2022-03-14
14 | ### Changed
15 | - Merged [PR #87](https://github.com/CHB61/multi_select_flutter/pull/87). Thanks @benyaminbeyzaie
16 |
17 | ## [4.1.1] - 2022-03-14
18 | ### Changed
19 | - Created a bug in 4.0.0 by removing the 'collection' dependency. It's only used to call firstWhereOrNull in _buildInheritedChipDisplay in both MultiSelectDialogField and MultiSelectBottomSheetField. This function is meant to automatically build the chips, but it may be changed or removed in the future as I don't think it is really necessary, and has caused some issues. For now, the package still depends on collection.
20 |
21 | ## [4.1.0] - 2022-03-14
22 | ### Changed
23 | - BREAKING: re-named the 'width' and 'height' fields to dialogWidth and dialogHeight.
24 | - Allow to adjust the width of the dialog.
25 |
26 | ### Added
27 | - param 'separateSelectedItems' which can be used with LIST type only.
28 |
29 | ## [4.0.0] - 2021-03-20
30 | ### Changed
31 | - Added null safety. Thanks to @ihancock for taking the initiative to apply this.
32 |
33 | ## [3.1.8] - 2021-02-15
34 | ### Fixed
35 | - [Slow with big list](https://github.com/CHB61/multi_select_flutter/issues/18). Using a ListView.builder solves this but haven't found a way to use ListView.builder when the listType is set to MultiSelectListType.CHIP. Currently when this is set, the widget renders a Wrap inside of a SingleChildScrollView. For now, if you have a big list, do not use `MultiSelectListType.CHIP`.
36 | ## [3.1.7] - 2021-02-14
37 | ### Changed
38 | - title param type from Text to Widget
39 | ### Fixed
40 | - Missing generic type `` on MultiSelectChipDisplay declarations
41 | - MultiSelectDialogField.dart line 33 in MultiSelectDialogField
42 | - MultiSelectDialogField.dart line 181 in _MultiSelectDialogFieldView
43 | - MultiSelectBottomSheetField.dart line 69 in MultiSelectBottomSheetField
44 | - MultiSelectBottomSheetField.dart line 209 _MultiSelectBottomSheetFieldView
45 |
46 | This would cause a type error if you were to specify the type of a MultiSelectChipDisplay that's part of a MultiSelectDialogField or MultiSelectBottomSheetField.
47 |
48 | ```dart
49 | MultiSelectBottomSheetField(
50 | onConfirm: (values) {
51 | _selectedAnimals = values;
52 | },
53 | items: _items,
54 | chipDisplay: MultiSelectChipDisplay( // supplying the runtime type here will cause error
55 | onTap: (item) {
56 | _selectedAnimals.remove(item);
57 | return _selectedAnimals;
58 | },
59 | ),
60 | )
61 | ```
62 | The type being supplied plus returning a List would mean that the onTap function is now `List Function(Animal)`
63 | However, since the declaration of the chipDisplay was missing the type (ex. MultiSelectChipDisplay chipDisplay;), it is expecting onTap to be
64 | `dynamic Function(dynamic)`.
65 | ## [3.1.6] - 2020-12-05
66 | ### Changed
67 | - Autofocus search textfield when search icon is tapped.
68 |
69 | ## [3.1.5] - 2020-12-01
70 | ### Fixed
71 | - Readme tables
72 | ## [3.1.4] - 2020-11-30
73 | ### Added
74 | - A constructor MultiSelectChipDisplay.none() which disables the default chipDisplay that is part of MultiSelectDialogField and
75 | MultiSelectBottomSheetField.
76 | ### Fixed
77 | - When using a MultiSelectDialogField with pre-selected values, can't remove from the chipDisplay until after first confirm.
78 | - The onConfirm function provides a reference to the list of selected values that MultiSelectDialogField uses.
79 | - If you have pre-selected values set, you can now simply return the list of updated values in the onTap of the
80 | MultiSelectChipDisplay.
81 | ## [3.1.3] - 2020-10-09
82 | ### Added
83 | - param `chipWidth` for MultiSelectChipDisplay and MultiSelectChipField. When this is set, overflowing text will show ellipses.
84 |
85 |
86 | ## [3.1.2] - 2020-10-09
87 | ### Added
88 | - param `scroll`, `scrollBar`, `height` for MultiSelectChipDisplay.
89 |
90 | ## [3.1.1] - 2020-10-08
91 | ### Added
92 | - param `showHeader` for MultiSelectChipField allowing it to be completely removed if so desired.
93 |
94 | ## [3.1.0] - 2020-10-04
95 | ### Fixed
96 | - `type Color is not a subtype of type bool` when selecting item in MultiSelectBottomSheet
97 | - These errors appear in cases that use conditional expressions like this: `widget.selectedColor ?? widget.var != null ? widget.var.func() : widget.otherVar`.
98 | - Show selected chips in MultiSelectChipDisplay when `initialValue` is set
99 | - Previously if you set the initialValue on a MultiSelectBottomSheetField, it would show the selected items in the bottomsheet
100 | but not in the chipDisplay.
101 |
102 | ### Added
103 | - param `checkColor` for dialog and bottomsheet widgets.
104 |
105 | ### Changed
106 | - BREAKING: Replace `chipColor` with `unselectedColor` for dialog and bottomsheet widgets. Apply the color to unselected checkbox border or the
107 | unselected chip body depending which `listType` is used.
108 | - BREAKING: Replace deprecated `autovalidate` with `autovalidateMode` on FormField widgets
109 |
110 | ## [3.0.1] - 2020-09-28
111 | ### Fixed
112 | - `type List is not a subtype of type bool` when using chipDisplay param on MultiSelectBottomSheet.
113 |
114 | ## [3.0.0] - 2020-09-27
115 | ### Added
116 | - MultiSelectChipField
117 | - Similar to MultiSelectChipDisplay except that it is the primary
118 | interface to select items
119 | - supports scroll or wrap, autoscroll, formfield features, searchable, custom items
120 |
121 | ### Changed
122 | - MultiSelectDialogField and MultiSelectBottomSheetField now come with a default `chipDisplay`.
123 | - User no longer needs `chipDisplay` param to have a MultiSelectChipDisplay, a MultiSelectDialogField with only `items` specified is enough.
124 | - User can override or remove it. To remove it, override with MultiSelectChipDisplay(items: [])
125 | - User no longer needs to specify `items` on MultiSelectChipDisplay when using it as a chipDisplay param
126 | - Combine Field/FormField widgets
127 | - Instead of having 2 widgets e.g. MultiSelectDialogField and MultiSelectDialogFormField,
128 | move the features of MultiSelectDialogFormField into MultiSelectDialogField.
129 | - Made MultiSelectChipDisplay stateless
130 | - Renamed searchPlaceholder to searchHint
131 | - Removed opacity param from MultiSelectChipDisplay.
132 |
133 | ## [2.3.0] - 2020-09-14
134 | ### Fixed
135 | - An error was being produced when using the `icon` param in MultiSelectChipDisplay with no `colorator` applied.
136 | - When no `title` is provided in MultiSelectDialogField, default title of Text("Select") should be provided, not a String of "Select"
137 |
138 | ### Changed
139 | - BREAKING: Removed `chipOpacity` from MultiSelectDialog and MultiSelectBottomSheet widgets.
140 | - can simply set the opacity along with the color.
141 |
142 | ### Added
143 | - Param `selectedItemsTextStyle` that applies to dialog / bottomsheet, list and chip.
144 |
145 |
146 | ## [2.2.0] - 2020-08-31
147 | ### Added
148 | A number of parameters that allow more customizations.
149 | - `backgroundColor`
150 | - `chipColor`
151 | - `chipOpacity`
152 | - `searchIcon`, `closeSearchIcon`
153 | - `itemsTextStyle`, `searchTextStyle`, `searchHintStyle`
154 | - `icon` and `shape` for MultiSelectChipDisplay
155 |
156 | ## [2.1.1] - 2020-08-27
157 | ### Changed
158 | - When colorator is applied to Field or FormField, apply the same colorator to the chipDisplay if there is one.
159 | Previously, when using a MultiSelectDialogField with a chipDisplay and you wanted the same effect both within
160 | the dialog and on the chipDisplay, you would have to define the colorator for both widgets. This is repetitive
161 | and not ideal. Now, the MultiSelectChipDisplay inherits the colorator from the parent field, and can still
162 | override that with the use of its own colorator.
163 |
164 | ## [2.1.0] - 2020-08-24
165 | ### Added
166 | - `colorator` param for all widgets. Set the color of individual items based on their value.
167 | - works like FormField's validator
168 | - takes a function in which you compare the value
169 | - return a color based on the value
170 | - return null if no conditions satisfied
171 | - applies to selected chips and checkboxes
172 | - `opacity` param for MultiSelectChipDisplay
173 |
174 | ### Changed
175 | - MultiSelectChipDisplay's `chipColor` param now automatically sets the opacity to 0.33 and sets the text to the same color but with full opacity.
176 | - previously you would have to set the chipColor to `Colors.blue.withOpacity(0.33)`
177 | and textStyle to `TextStyle(Colors.blue)` to achieve this simple look.
178 | - you can still override this if you want different colored text by using the `textStyle` param
179 | - and you can override the default 0.33 opacity of the chip body with the new `opacity` param
180 | - set the color of the FormField widgets bottom border to `selectedColor` if there is one
181 | - set the color of confirm/cancel buttons to `selectedColor` if there is one
182 |
183 |
184 | ## [2.0.3] - 2020-08-22
185 | ### Added
186 | - parameter `searchPlaceholder` to replace the default "Search" text.
187 |
188 | ### Fixed
189 | - `selectedColor` wasn't being passed to MultiSelectDialog when using MultiSelectDialogField or MultiSelectDialogFormField
190 |
191 | ## [2.0.2] - 2020-08-17
192 | ### Added
193 | - dartdoc comments
194 |
195 | ## [2.0.1] - 2020-08-17
196 | ### Added
197 | - `selectedColor` param which controls the color of the selected checkbox / chips within a dialog / bottomsheet
198 | - `height` param to MultiSelectDialog widgets.
199 |
200 | ### Changed
201 | - Set the default color of the confirmText and cancelText to primary for BottomSheet widgets.
202 |
203 | ### Fixed
204 | - onSelectionChanged wasn't being called for all widgets.
205 |
206 | ## [2.0.0] - 2020-08-16
207 | ### Added
208 | - `MultiSelectBottomSheet`, `MultiSelectBottomSheetField`, `MultiSelectBottomSheetFormField`
209 | - `barrierColor` param to MultiSelectDialog
210 |
211 | ### Changed
212 | - The addition of the MultiSelectBottomSheet widgets prompted a bit of a re-write in order to de-couple widgets. Didn't want the generic MultiSelectField to be responsible for both types (dialog, bottomsheet). The new structure makes more sense and is easier to work with.
213 | - MultiSelectField replaced with MultiSelectDialogField / MultiSelectBottomSheetField.
214 | - MultiSelectListDialog and MultiSelectChipDialog have been replaced with MultiSelectDialog.
215 | - dialogType replaced with listType, now accepts MultiSelectListType instead of MultiSelectDialogType.
216 | - Improved the look of validator text, matches the look of TextFormField validator style much more closely.
217 | - buttonText, confirmText, cancelText all now accept a Text widget instead of a string, allowing the removal of the textStyle param.
218 |
219 | ## [1.0.6] - 2020-08-14
220 |
221 | ### Fixed
222 |
223 | - Implement scrolling in MultiSelectChipDialog.
224 | - When the source list was large enough, the chip dialog would just overflow but it is now scrollable.
225 |
226 | ### Changed
227 |
228 | - `title` is no longer required for List / Chip dialogs, default to "Select".
229 | - `initialSelectedItems` is now required for List / Chip dialogs.
230 | - `items` is now required for MultiSelectChipDisplay.
231 | - Removed iconSize - can set icon size as param of Icon.
232 | - Removed `state` as a parameter of MultiSelectField and created a constructor `MultiSelectField.withState()` that gets called by MultiSelectFormField.
233 | - `state` never needs to be set by the user, and would do nothing if set explicitly, so it shouldn't be a named param on the default constructor.
234 | - Updated docs
235 |
236 | ## [1.0.5] - 2020-07-10
237 |
238 | ### Fixed
239 |
240 | - A bug was introduced in version 1.0.4 that caused the MultiSelectFormField to not highlight any of the selected values in the dialog.
241 |
242 | ### Added
243 |
244 | - Boolean parameter 'searchable'. Useful for larger lists, the searchable parameter enables a search icon in the dialog which shows a search bar that lets you query the list.
245 | - String parameters for 'confirmText' and 'cancelText'. This is important for users who want text other than 'OK' and 'CANCEL', especially for other languages and alphabets.
246 |
247 | ## [1.0.4] - 2020-07-06
248 |
249 | ### Changed
250 |
251 | - Allow user to set initial selected values for MultiSelectField and MultiSelectFormField by adding param 'initialValue' to MultiSelectField, and enabled the same functionality for MultiSelectFormField by passing its existing 'initialValue' param to the MultiSelectField's new 'initialValue' param.
252 | - MultiSelectFormField creates a MultiSelectField which never had an 'initialValue' param that could be set by the user. Even if 'initialValue' was set on the MultiSelectFormField, it never got passed to the MultiSelectField that it creates. Now it does.
253 | - Previously when using a MultiSelectField or FormField, the values in the MultiSelectListDialog / MultiSelectChipDialog were only being stored internally when a user confirms the values. Now the initial values can be set before any user interaction has occurred.
254 | - Another use case is when a MultiSelectField is being re-inserted into the widget tree (such as one inside of a PersistentBottomSheet), and if the developer wants the previously selected values to remain after the bottomsheet re-opens, they can use this parameter to achieve that.
255 | - Updated example app
256 |
257 | ## [1.0.3] - 2020-06-21
258 |
259 | ### Changed
260 |
261 | - Updated pubspec.yaml project description and added homepage link
262 |
263 | ## [1.0.2] - 2020-06-20
264 |
265 | ### Added
266 |
267 | - Example project
268 |
269 | - analysis_options.yaml
270 |
271 | ## [1.0.1] - 2020-06-20
272 |
273 | ### Changed
274 |
275 | - Added readme
276 |
277 | ## [1.0.0] - 2020-06-20
278 |
279 | ### Added
280 |
281 | - Creation of MultiSelect package
282 |
283 | - Widgets include MultiSelectListDialog, MultiSelectChipDialog, MultiSelectChipDisplay, MultiSelectField, MultiSelectFormField.
--------------------------------------------------------------------------------
/lib/bottom_sheet/multi_select_bottom_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../util/multi_select_item.dart';
3 | import '../util/multi_select_actions.dart';
4 | import '../util/multi_select_list_type.dart';
5 |
6 | /// A bottom sheet widget containing either a classic checkbox style list, or a chip style list.
7 | class MultiSelectBottomSheet extends StatefulWidget
8 | with MultiSelectActions {
9 | /// List of items to select from.
10 | final List> items;
11 |
12 | /// The list of selected values before interaction.
13 | final List initialValue;
14 |
15 | /// The text at the top of the BottomSheet.
16 | final Widget? title;
17 |
18 | /// Fires when the an item is selected / unselected.
19 | final void Function(List)? onSelectionChanged;
20 |
21 | /// Fires when confirm is tapped.
22 | final void Function(List)? onConfirm;
23 |
24 | /// Toggles search functionality.
25 | final bool searchable;
26 |
27 | /// Text on the confirm button.
28 | final Text? confirmText;
29 |
30 | /// Text on the cancel button.
31 | final Text? cancelText;
32 |
33 | /// An enum that determines which type of list to render.
34 | final MultiSelectListType? listType;
35 |
36 | /// Sets the color of the checkbox or chip when it's selected.
37 | final Color? selectedColor;
38 |
39 | /// Set the initial height of the BottomSheet.
40 | final double? initialChildSize;
41 |
42 | /// Set the minimum height threshold of the BottomSheet before it closes.
43 | final double? minChildSize;
44 |
45 | /// Set the maximum height of the BottomSheet.
46 | final double? maxChildSize;
47 |
48 | /// Set the placeholder text of the search field.
49 | final String? searchHint;
50 |
51 | /// A function that sets the color of selected items based on their value.
52 | /// It will either set the chip color, or the checkbox color depending on the list type.
53 | final Color? Function(T)? colorator;
54 |
55 | /// Color of the chip body or checkbox border while not selected.
56 | final Color? unselectedColor;
57 |
58 | /// Icon button that shows the search field.
59 | final Icon? searchIcon;
60 |
61 | /// Icon button that hides the search field
62 | final Icon? closeSearchIcon;
63 |
64 | /// Style the text on the chips or list tiles.
65 | final TextStyle? itemsTextStyle;
66 |
67 | /// Style the text on the selected chips or list tiles.
68 | final TextStyle? selectedItemsTextStyle;
69 |
70 | /// Style the search text.
71 | final TextStyle? searchTextStyle;
72 |
73 | /// Style the search hint.
74 | final TextStyle? searchHintStyle;
75 |
76 | /// Moves the selected items to the top of the list.
77 | final bool separateSelectedItems;
78 |
79 | /// Set the color of the check in the checkbox
80 | final Color? checkColor;
81 |
82 | MultiSelectBottomSheet({
83 | required this.items,
84 | required this.initialValue,
85 | this.title,
86 | this.onSelectionChanged,
87 | this.onConfirm,
88 | this.listType,
89 | this.cancelText,
90 | this.confirmText,
91 | this.searchable = false,
92 | this.selectedColor,
93 | this.initialChildSize,
94 | this.minChildSize,
95 | this.maxChildSize,
96 | this.colorator,
97 | this.unselectedColor,
98 | this.searchIcon,
99 | this.closeSearchIcon,
100 | this.itemsTextStyle,
101 | this.searchTextStyle,
102 | this.searchHint,
103 | this.searchHintStyle,
104 | this.selectedItemsTextStyle,
105 | this.separateSelectedItems = false,
106 | this.checkColor,
107 | });
108 |
109 | @override
110 | _MultiSelectBottomSheetState createState() =>
111 | _MultiSelectBottomSheetState(items);
112 | }
113 |
114 | class _MultiSelectBottomSheetState extends State> {
115 | List _selectedValues = [];
116 | bool _showSearch = false;
117 | List> _items;
118 |
119 | _MultiSelectBottomSheetState(this._items);
120 |
121 | @override
122 | void initState() {
123 | super.initState();
124 | _selectedValues.addAll(widget.initialValue);
125 |
126 | for (int i = 0; i < _items.length; i++) {
127 | _items[i].selected = false;
128 | if (_selectedValues.contains(_items[i].value)) {
129 | _items[i].selected = true;
130 | }
131 | }
132 |
133 | if (widget.separateSelectedItems) {
134 | _items = widget.separateSelected(_items);
135 | }
136 | }
137 |
138 | /// Returns a CheckboxListTile
139 | Widget _buildListItem(MultiSelectItem item) {
140 | return Theme(
141 | data: ThemeData(
142 | unselectedWidgetColor: widget.unselectedColor ?? Colors.black54,
143 | ),
144 | child: CheckboxListTile(
145 | checkColor: widget.checkColor,
146 | value: item.selected,
147 | activeColor: widget.colorator != null
148 | ? widget.colorator!(item.value) ?? widget.selectedColor
149 | : widget.selectedColor,
150 | title: Text(
151 | item.label,
152 | style: item.selected
153 | ? widget.selectedItemsTextStyle
154 | : widget.itemsTextStyle,
155 | ),
156 | controlAffinity: ListTileControlAffinity.leading,
157 | onChanged: (checked) {
158 | setState(() {
159 | _selectedValues = widget.onItemCheckedChange(
160 | _selectedValues, item.value, checked!);
161 |
162 | if (checked) {
163 | item.selected = true;
164 | } else {
165 | item.selected = false;
166 | }
167 | if (widget.separateSelectedItems) {
168 | _items = widget.separateSelected(_items);
169 | }
170 | });
171 | if (widget.onSelectionChanged != null) {
172 | widget.onSelectionChanged!(_selectedValues);
173 | }
174 | },
175 | ),
176 | );
177 | }
178 |
179 | /// Returns a ChoiceChip
180 | Widget _buildChipItem(MultiSelectItem item) {
181 | return Container(
182 | padding: const EdgeInsets.all(2.0),
183 | child: ChoiceChip(
184 | backgroundColor: widget.unselectedColor,
185 | selectedColor:
186 | widget.colorator != null && widget.colorator!(item.value) != null
187 | ? widget.colorator!(item.value)
188 | : widget.selectedColor != null
189 | ? widget.selectedColor
190 | : Theme.of(context).primaryColor.withOpacity(0.35),
191 | label: Text(
192 | item.label,
193 | style: _selectedValues.contains(item.value)
194 | ? TextStyle(
195 | color: widget.selectedItemsTextStyle?.color ??
196 | widget.colorator?.call(item.value) ??
197 | widget.selectedColor?.withOpacity(1) ??
198 | Theme.of(context).primaryColor,
199 | fontSize: widget.selectedItemsTextStyle != null
200 | ? widget.selectedItemsTextStyle!.fontSize
201 | : null,
202 | )
203 | : widget.itemsTextStyle,
204 | ),
205 | selected: item.selected,
206 | onSelected: (checked) {
207 | if (checked) {
208 | item.selected = true;
209 | } else {
210 | item.selected = false;
211 | }
212 | setState(() {
213 | _selectedValues = widget.onItemCheckedChange(
214 | _selectedValues, item.value, checked);
215 | });
216 | if (widget.onSelectionChanged != null) {
217 | widget.onSelectionChanged!(_selectedValues);
218 | }
219 | },
220 | ),
221 | );
222 | }
223 |
224 | @override
225 | Widget build(BuildContext context) {
226 | return Container(
227 | padding:
228 | EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
229 | child: DraggableScrollableSheet(
230 | initialChildSize: widget.initialChildSize ?? 0.3,
231 | minChildSize: widget.minChildSize ?? 0.3,
232 | maxChildSize: widget.maxChildSize ?? 0.6,
233 | expand: false,
234 | builder: (BuildContext context, ScrollController scrollController) {
235 | return Column(
236 | children: [
237 | Padding(
238 | padding: const EdgeInsets.all(10),
239 | child: Row(
240 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
241 | children: [
242 | _showSearch
243 | ? Expanded(
244 | child: Container(
245 | padding: const EdgeInsets.only(left: 10),
246 | child: TextField(
247 | autofocus: true,
248 | style: widget.searchTextStyle,
249 | decoration: InputDecoration(
250 | hintStyle: widget.searchHintStyle,
251 | hintText: widget.searchHint ?? "Search",
252 | focusedBorder: UnderlineInputBorder(
253 | borderSide: BorderSide(
254 | color: widget.selectedColor ??
255 | Theme.of(context).primaryColor),
256 | ),
257 | ),
258 | onChanged: (val) {
259 | List> filteredList = [];
260 | filteredList = widget.updateSearchQuery(
261 | val, widget.items);
262 | setState(() {
263 | if (widget.separateSelectedItems) {
264 | _items =
265 | widget.separateSelected(filteredList);
266 | } else {
267 | _items = filteredList;
268 | }
269 | });
270 | },
271 | ),
272 | ),
273 | )
274 | : widget.title ??
275 | Text(
276 | "Select",
277 | style: TextStyle(fontSize: 18),
278 | ),
279 | widget.searchable
280 | ? IconButton(
281 | icon: _showSearch
282 | ? widget.closeSearchIcon ?? Icon(Icons.close)
283 | : widget.searchIcon ?? Icon(Icons.search),
284 | onPressed: () {
285 | setState(() {
286 | _showSearch = !_showSearch;
287 | if (!_showSearch) {
288 | if (widget.separateSelectedItems) {
289 | _items =
290 | widget.separateSelected(widget.items);
291 | } else {
292 | _items = widget.items;
293 | }
294 | }
295 | });
296 | },
297 | )
298 | : Padding(
299 | padding: EdgeInsets.all(15),
300 | ),
301 | ],
302 | ),
303 | ),
304 | Expanded(
305 | child: widget.listType == null ||
306 | widget.listType == MultiSelectListType.LIST
307 | ? ListView.builder(
308 | controller: scrollController,
309 | itemCount: _items.length,
310 | itemBuilder: (context, index) {
311 | return _buildListItem(_items[index]);
312 | },
313 | )
314 | : SingleChildScrollView(
315 | controller: scrollController,
316 | child: Container(
317 | padding: EdgeInsets.all(10),
318 | child: Wrap(
319 | children: _items.map(_buildChipItem).toList(),
320 | ),
321 | ),
322 | ),
323 | ),
324 | Container(
325 | padding: EdgeInsets.all(2),
326 | child: Row(
327 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
328 | children: [
329 | Expanded(
330 | child: TextButton(
331 | onPressed: () {
332 | widget.onCancelTap(context, widget.initialValue);
333 | },
334 | child: widget.cancelText ??
335 | Text(
336 | "CANCEL",
337 | style: TextStyle(
338 | color: (widget.selectedColor != null &&
339 | widget.selectedColor !=
340 | Colors.transparent)
341 | ? widget.selectedColor!.withOpacity(1)
342 | : Theme.of(context).primaryColor,
343 | ),
344 | ),
345 | ),
346 | ),
347 | SizedBox(width: 10),
348 | Expanded(
349 | child: TextButton(
350 | onPressed: () {
351 | widget.onConfirmTap(
352 | context, _selectedValues, widget.onConfirm);
353 | },
354 | child: widget.confirmText ??
355 | Text(
356 | "OK",
357 | style: TextStyle(
358 | color: (widget.selectedColor != null &&
359 | widget.selectedColor !=
360 | Colors.transparent)
361 | ? widget.selectedColor!.withOpacity(1)
362 | : Theme.of(context).primaryColor,
363 | ),
364 | ),
365 | ),
366 | ),
367 | ],
368 | ),
369 | ),
370 | ],
371 | );
372 | },
373 | ),
374 | );
375 | }
376 | }
377 |
--------------------------------------------------------------------------------
/lib/dialog/multi_select_dialog_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:collection/collection.dart' show IterableExtension;
2 | import 'package:flutter/material.dart';
3 | import '../util/multi_select_list_type.dart';
4 | import '../util/multi_select_item.dart';
5 | import '../chip_display/multi_select_chip_display.dart';
6 | import 'mult_select_dialog.dart';
7 |
8 | /// A customizable InkWell widget that opens the MultiSelectDialog
9 | // ignore: must_be_immutable
10 | class MultiSelectDialogField extends FormField> {
11 | /// An enum that determines which type of list to render.
12 | final MultiSelectListType? listType;
13 |
14 | /// Style the Container that makes up the field.
15 | final BoxDecoration? decoration;
16 |
17 | /// Set text that is displayed on the button.
18 | final Text? buttonText;
19 |
20 | /// Specify the button icon.
21 | final Icon? buttonIcon;
22 |
23 | /// The text at the top of the dialog.
24 | final Widget? title;
25 |
26 | /// List of items to select from.
27 | final List> items;
28 |
29 | /// Fires when the an item is selected / unselected.
30 | final void Function(List)? onSelectionChanged;
31 |
32 | /// Overrides the default MultiSelectChipDisplay attached to this field.
33 | /// If you want to remove it, use MultiSelectChipDisplay.none().
34 | final MultiSelectChipDisplay? chipDisplay;
35 |
36 | /// The list of selected values before interaction.
37 | final List initialValue;
38 |
39 | /// Fires when confirm is tapped.
40 | final void Function(List) onConfirm;
41 |
42 | /// Toggles search functionality.
43 | final bool searchable;
44 |
45 | /// Text on the confirm button.
46 | final Text? confirmText;
47 |
48 | /// Text on the cancel button.
49 | final Text? cancelText;
50 |
51 | /// Set the color of the space outside the BottomSheet.
52 | final Color? barrierColor;
53 |
54 | /// Sets the color of the checkbox or chip when it's selected.
55 | final Color? selectedColor;
56 |
57 | /// Sets a fixed height on the dialog.
58 | final double? dialogHeight;
59 |
60 | /// Sets a fixed width on the dialog.
61 | final double? dialogWidth;
62 |
63 | /// Set the placeholder text of the search field.
64 | final String? searchHint;
65 |
66 | /// A function that sets the color of selected items based on their value.
67 | /// It will either set the chip color, or the checkbox color depending on the list type.
68 | final Color Function(V)? colorator;
69 |
70 | /// Set the background color of the dialog.
71 | final Color? backgroundColor;
72 |
73 | /// Color of the chip body or checkbox border while not selected.
74 | final Color? unselectedColor;
75 |
76 | /// Replaces the deafult search icon when searchable is true.
77 | final Icon? searchIcon;
78 |
79 | /// Replaces the default close search icon when searchable is true.
80 | final Icon? closeSearchIcon;
81 |
82 | /// Style the text on the chips or list tiles.
83 | final TextStyle? itemsTextStyle;
84 |
85 | /// Style the text on the selected chips or list tiles.
86 | final TextStyle? selectedItemsTextStyle;
87 |
88 | /// Style the text that is typed into the search field.
89 | final TextStyle? searchTextStyle;
90 |
91 | /// Style the search hint.
92 | final TextStyle? searchHintStyle;
93 |
94 | /// Moves the selected items to the top of the list.
95 | final bool separateSelectedItems;
96 |
97 | /// Set the color of the check in the checkbox
98 | final Color? checkColor;
99 |
100 | /// Whether the user can dismiss the widget by tapping outside
101 | final bool isDismissible;
102 |
103 | final AutovalidateMode autovalidateMode;
104 | final FormFieldValidator>? validator;
105 | final FormFieldSetter>? onSaved;
106 | final GlobalKey? key;
107 | FormFieldState>? state;
108 |
109 | MultiSelectDialogField({
110 | required this.items,
111 | required this.onConfirm,
112 | this.title,
113 | this.buttonText,
114 | this.buttonIcon,
115 | this.listType,
116 | this.decoration,
117 | this.onSelectionChanged,
118 | this.chipDisplay,
119 | this.searchable = false,
120 | this.confirmText,
121 | this.cancelText,
122 | this.barrierColor,
123 | this.selectedColor,
124 | this.searchHint,
125 | this.dialogHeight,
126 | this.dialogWidth,
127 | this.colorator,
128 | this.backgroundColor,
129 | this.unselectedColor,
130 | this.searchIcon,
131 | this.closeSearchIcon,
132 | this.itemsTextStyle,
133 | this.searchTextStyle,
134 | this.searchHintStyle,
135 | this.selectedItemsTextStyle,
136 | this.separateSelectedItems = false,
137 | this.checkColor,
138 | this.isDismissible = true,
139 | this.onSaved,
140 | this.validator,
141 | this.initialValue = const [],
142 | this.autovalidateMode = AutovalidateMode.disabled,
143 | this.key,
144 | }) : super(
145 | key: key,
146 | onSaved: onSaved,
147 | validator: validator,
148 | autovalidateMode: autovalidateMode,
149 | initialValue: initialValue,
150 | builder: (FormFieldState> state) {
151 | _MultiSelectDialogFieldView field =
152 | _MultiSelectDialogFieldView(
153 | title: title,
154 | items: items,
155 | buttonText: buttonText,
156 | buttonIcon: buttonIcon,
157 | chipDisplay: chipDisplay,
158 | decoration: decoration,
159 | listType: listType,
160 | onConfirm: onConfirm,
161 | onSelectionChanged: onSelectionChanged,
162 | initialValue: initialValue,
163 | searchable: searchable,
164 | confirmText: confirmText,
165 | cancelText: cancelText,
166 | barrierColor: barrierColor,
167 | selectedColor: selectedColor,
168 | searchHint: searchHint,
169 | dialogHeight: dialogHeight,
170 | dialogWidth: dialogWidth,
171 | colorator: colorator,
172 | backgroundColor: backgroundColor,
173 | unselectedColor: unselectedColor,
174 | searchIcon: searchIcon,
175 | closeSearchIcon: closeSearchIcon,
176 | itemsTextStyle: itemsTextStyle,
177 | searchTextStyle: searchTextStyle,
178 | searchHintStyle: searchHintStyle,
179 | selectedItemsTextStyle: selectedItemsTextStyle,
180 | separateSelectedItems: separateSelectedItems,
181 | checkColor: checkColor,
182 | isDismissible: isDismissible,
183 | );
184 | return _MultiSelectDialogFieldView._withState(field, state);
185 | });
186 | }
187 |
188 | // ignore: must_be_immutable
189 | class _MultiSelectDialogFieldView extends StatefulWidget {
190 | final MultiSelectListType? listType;
191 | final BoxDecoration? decoration;
192 | final Text? buttonText;
193 | final Icon? buttonIcon;
194 | final Widget? title;
195 | final List> items;
196 | final void Function(List)? onSelectionChanged;
197 | final MultiSelectChipDisplay? chipDisplay;
198 | final List initialValue;
199 | final void Function(List)? onConfirm;
200 | final bool? searchable;
201 | final Text? confirmText;
202 | final Text? cancelText;
203 | final Color? barrierColor;
204 | final Color? selectedColor;
205 | final double? dialogHeight;
206 | final double? dialogWidth;
207 | final String? searchHint;
208 | final Color Function(V)? colorator;
209 | final Color? backgroundColor;
210 | final Color? unselectedColor;
211 | final Icon? searchIcon;
212 | final Icon? closeSearchIcon;
213 | final TextStyle? itemsTextStyle;
214 | final TextStyle? selectedItemsTextStyle;
215 | final TextStyle? searchTextStyle;
216 | final TextStyle? searchHintStyle;
217 | final bool separateSelectedItems;
218 | final Color? checkColor;
219 | final bool isDismissible;
220 | FormFieldState>? state;
221 |
222 | _MultiSelectDialogFieldView({
223 | required this.items,
224 | this.title,
225 | this.buttonText,
226 | this.buttonIcon,
227 | this.listType,
228 | this.decoration,
229 | this.onSelectionChanged,
230 | this.onConfirm,
231 | this.chipDisplay,
232 | this.initialValue = const [],
233 | this.searchable,
234 | this.confirmText,
235 | this.cancelText,
236 | this.barrierColor,
237 | this.selectedColor,
238 | this.searchHint,
239 | this.dialogHeight,
240 | this.dialogWidth,
241 | this.colorator,
242 | this.backgroundColor,
243 | this.unselectedColor,
244 | this.searchIcon,
245 | this.closeSearchIcon,
246 | this.itemsTextStyle,
247 | this.searchTextStyle,
248 | this.searchHintStyle,
249 | this.selectedItemsTextStyle,
250 | this.separateSelectedItems = false,
251 | this.checkColor,
252 | required this.isDismissible,
253 | });
254 |
255 | /// This constructor allows a FormFieldState to be passed in. Called by MultiSelectDialogField.
256 | _MultiSelectDialogFieldView._withState(
257 | _MultiSelectDialogFieldView field, FormFieldState> state)
258 | : items = field.items,
259 | title = field.title,
260 | buttonText = field.buttonText,
261 | buttonIcon = field.buttonIcon,
262 | listType = field.listType,
263 | decoration = field.decoration,
264 | onSelectionChanged = field.onSelectionChanged,
265 | onConfirm = field.onConfirm,
266 | chipDisplay = field.chipDisplay,
267 | initialValue = field.initialValue,
268 | searchable = field.searchable,
269 | confirmText = field.confirmText,
270 | cancelText = field.cancelText,
271 | barrierColor = field.barrierColor,
272 | selectedColor = field.selectedColor,
273 | dialogHeight = field.dialogHeight,
274 | dialogWidth = field.dialogWidth,
275 | searchHint = field.searchHint,
276 | colorator = field.colorator,
277 | backgroundColor = field.backgroundColor,
278 | unselectedColor = field.unselectedColor,
279 | searchIcon = field.searchIcon,
280 | closeSearchIcon = field.closeSearchIcon,
281 | itemsTextStyle = field.itemsTextStyle,
282 | searchHintStyle = field.searchHintStyle,
283 | searchTextStyle = field.searchTextStyle,
284 | selectedItemsTextStyle = field.selectedItemsTextStyle,
285 | separateSelectedItems = field.separateSelectedItems,
286 | checkColor = field.checkColor,
287 | isDismissible = field.isDismissible,
288 | state = state;
289 |
290 | @override
291 | __MultiSelectDialogFieldViewState createState() =>
292 | __MultiSelectDialogFieldViewState();
293 | }
294 |
295 | class __MultiSelectDialogFieldViewState
296 | extends State<_MultiSelectDialogFieldView> {
297 | List _selectedItems = [];
298 |
299 | @override
300 | void initState() {
301 | super.initState();
302 | _selectedItems.addAll(widget.initialValue);
303 | }
304 |
305 | @override
306 | void didUpdateWidget(_MultiSelectDialogFieldView oldWidget) {
307 | super.didUpdateWidget(oldWidget);
308 |
309 | if (oldWidget.initialValue != widget.initialValue) {
310 | _selectedItems = [];
311 | _selectedItems.addAll(widget.initialValue);
312 |
313 | WidgetsBinding.instance.addPostFrameCallback((_) {
314 | widget.state!.didChange(_selectedItems);
315 | });
316 | }
317 | }
318 |
319 | Widget _buildInheritedChipDisplay() {
320 | List?> chipDisplayItems = [];
321 | chipDisplayItems = _selectedItems
322 | .map((e) =>
323 | widget.items.firstWhereOrNull((element) => e == element.value))
324 | .toList();
325 | chipDisplayItems.removeWhere((element) => element == null);
326 | if (widget.chipDisplay != null) {
327 | // if user has specified a chipDisplay, use its params
328 | if (widget.chipDisplay!.disabled!) {
329 | return Container();
330 | } else {
331 | return MultiSelectChipDisplay(
332 | items: chipDisplayItems,
333 | colorator: widget.chipDisplay!.colorator ?? widget.colorator,
334 | onTap: (item) {
335 | List? newValues;
336 | if (widget.chipDisplay!.onTap != null) {
337 | dynamic result = widget.chipDisplay!.onTap!(item);
338 | if (result is List) newValues = result;
339 | }
340 | if (newValues != null) {
341 | _selectedItems = newValues;
342 | if (widget.state != null) {
343 | widget.state!.didChange(_selectedItems);
344 | }
345 | }
346 | },
347 | decoration: widget.chipDisplay!.decoration,
348 | chipColor: widget.chipDisplay!.chipColor ??
349 | ((widget.selectedColor != null &&
350 | widget.selectedColor != Colors.transparent)
351 | ? widget.selectedColor!.withOpacity(0.35)
352 | : null),
353 | alignment: widget.chipDisplay!.alignment,
354 | textStyle: widget.chipDisplay!.textStyle,
355 | icon: widget.chipDisplay!.icon,
356 | shape: widget.chipDisplay!.shape,
357 | scroll: widget.chipDisplay!.scroll,
358 | scrollBar: widget.chipDisplay!.scrollBar,
359 | height: widget.chipDisplay!.height,
360 | chipWidth: widget.chipDisplay!.chipWidth,
361 | );
362 | }
363 | } else {
364 | // user didn't specify a chipDisplay, build the default
365 | return MultiSelectChipDisplay(
366 | items: chipDisplayItems,
367 | colorator: widget.colorator,
368 | chipColor: (widget.selectedColor != null &&
369 | widget.selectedColor != Colors.transparent)
370 | ? widget.selectedColor!.withOpacity(0.35)
371 | : null,
372 | );
373 | }
374 | }
375 |
376 | /// Calls showDialog() and renders a MultiSelectDialog.
377 | _showDialog(BuildContext ctx) async {
378 | await showDialog(
379 | barrierColor: widget.barrierColor,
380 | barrierDismissible: widget.isDismissible,
381 | context: context,
382 | builder: (ctx) {
383 | return MultiSelectDialog(
384 | checkColor: widget.checkColor,
385 | selectedItemsTextStyle: widget.selectedItemsTextStyle,
386 | searchHintStyle: widget.searchHintStyle,
387 | searchTextStyle: widget.searchTextStyle,
388 | itemsTextStyle: widget.itemsTextStyle,
389 | searchIcon: widget.searchIcon,
390 | closeSearchIcon: widget.closeSearchIcon,
391 | unselectedColor: widget.unselectedColor,
392 | backgroundColor: widget.backgroundColor,
393 | colorator: widget.colorator,
394 | searchHint: widget.searchHint,
395 | selectedColor: widget.selectedColor,
396 | onSelectionChanged: widget.onSelectionChanged,
397 | height: widget.dialogHeight,
398 | width: widget.dialogWidth,
399 | listType: widget.listType,
400 | items: widget.items,
401 | title: widget.title ?? const Text("Select"),
402 | initialValue: _selectedItems,
403 | searchable: widget.searchable ?? false,
404 | confirmText: widget.confirmText,
405 | cancelText: widget.cancelText,
406 | separateSelectedItems: widget.separateSelectedItems,
407 | onConfirm: (selected) {
408 | _selectedItems = selected;
409 | if (widget.state != null) {
410 | widget.state!.didChange(_selectedItems);
411 | }
412 | if (widget.onConfirm != null) widget.onConfirm!(_selectedItems);
413 | },
414 | );
415 | },
416 | );
417 | }
418 |
419 | @override
420 | Widget build(BuildContext context) {
421 | return Column(
422 | mainAxisAlignment: MainAxisAlignment.start,
423 | children: [
424 | InkWell(
425 | onTap: () {
426 | _showDialog(context);
427 | },
428 | child: Container(
429 | decoration: widget.state != null
430 | ? widget.decoration ??
431 | BoxDecoration(
432 | border: Border(
433 | bottom: BorderSide(
434 | color: widget.state != null && widget.state!.hasError
435 | ? Colors.red.shade800.withOpacity(0.6)
436 | : _selectedItems.isNotEmpty
437 | ? (widget.selectedColor != null &&
438 | widget.selectedColor !=
439 | Colors.transparent)
440 | ? widget.selectedColor!
441 | : Theme.of(context).primaryColor
442 | : Colors.black45,
443 | width: _selectedItems.isNotEmpty
444 | ? (widget.state != null && widget.state!.hasError)
445 | ? 1.4
446 | : 1.8
447 | : 1.2,
448 | ),
449 | ),
450 | )
451 | : widget.decoration,
452 | padding: const EdgeInsets.all(10),
453 | child: Row(
454 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
455 | children: [
456 | widget.buttonText ?? const Text("Select"),
457 | widget.buttonIcon ?? const Icon(Icons.arrow_downward),
458 | ],
459 | ),
460 | ),
461 | ),
462 | _buildInheritedChipDisplay(),
463 | widget.state != null && widget.state!.hasError
464 | ? const SizedBox(height: 5)
465 | : Container(),
466 | widget.state != null && widget.state!.hasError
467 | ? Row(
468 | children: [
469 | Padding(
470 | padding: const EdgeInsets.only(left: 4),
471 | child: Text(
472 | widget.state!.errorText!,
473 | style: TextStyle(
474 | color: Colors.red[800],
475 | fontSize: 12.5,
476 | ),
477 | ),
478 | ),
479 | ],
480 | )
481 | : Container(),
482 | ],
483 | );
484 | }
485 | }
486 |
--------------------------------------------------------------------------------
/lib/bottom_sheet/multi_select_bottom_sheet_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:collection/collection.dart' show IterableExtension;
2 | import 'package:flutter/material.dart';
3 | import '../util/multi_select_list_type.dart';
4 | import '../chip_display/multi_select_chip_display.dart';
5 | import '../util/multi_select_item.dart';
6 | import 'multi_select_bottom_sheet.dart';
7 |
8 | /// A customizable InkWell widget that opens the MultiSelectBottomSheet
9 | // ignore: must_be_immutable
10 | class MultiSelectBottomSheetField extends FormField> {
11 | /// Style the Container that makes up the field.
12 | final BoxDecoration? decoration;
13 |
14 | /// Set text that is displayed on the button.
15 | final Text? buttonText;
16 |
17 | /// Specify the button icon.
18 | final Icon? buttonIcon;
19 |
20 | /// List of items to select from.
21 | final List> items;
22 |
23 | /// The list of selected values before interaction.
24 | final List initialValue;
25 |
26 | /// The text at the top of the dialog.
27 | final Widget? title;
28 |
29 | /// Fires when the an item is selected / unselected.
30 | final void Function(List)? onSelectionChanged;
31 |
32 | /// Fires when confirm is tapped.
33 | final void Function(List) onConfirm;
34 |
35 | /// Toggles search functionality.
36 | final bool searchable;
37 |
38 | /// Text on the confirm button.
39 | final Text? confirmText;
40 |
41 | /// Text on the cancel button.
42 | final Text? cancelText;
43 |
44 | /// An enum that determines which type of list to render.
45 | final MultiSelectListType? listType;
46 |
47 | /// Sets the color of the checkbox or chip body when selected.
48 | final Color? selectedColor;
49 |
50 | /// Set the hint text of the search field.
51 | final String? searchHint;
52 |
53 | /// Set the initial height of the BottomSheet.
54 | final double? initialChildSize;
55 |
56 | /// Set the minimum height threshold of the BottomSheet before it closes.
57 | final double? minChildSize;
58 |
59 | /// Set the maximum height of the BottomSheet.
60 | final double? maxChildSize;
61 |
62 | /// Apply a ShapeBorder to alter the edges of the BottomSheet.
63 | final ShapeBorder? shape;
64 |
65 | /// Set the color of the space outside the BottomSheet.
66 | final Color? barrierColor;
67 |
68 | /// Overrides the default MultiSelectChipDisplay attached to this field.
69 | /// If you want to remove it, use MultiSelectChipDisplay.none().
70 | final MultiSelectChipDisplay? chipDisplay;
71 |
72 | /// A function that sets the color of selected items based on their value.
73 | /// It will either set the chip color, or the checkbox color depending on the list type.
74 | final Color Function(V)? colorator;
75 |
76 | /// Set the background color of the bottom sheet.
77 | final Color? backgroundColor;
78 |
79 | /// Color of the chip body or checkbox border while not selected.
80 | final Color? unselectedColor;
81 |
82 | /// Replaces the deafult search icon when searchable is true.
83 | final Icon? searchIcon;
84 |
85 | /// Replaces the default close search icon when searchable is true.
86 | final Icon? closeSearchIcon;
87 |
88 | /// The TextStyle of the items within the BottomSheet.
89 | final TextStyle? itemsTextStyle;
90 |
91 | /// Style the text on the selected chips or list tiles.
92 | final TextStyle? selectedItemsTextStyle;
93 |
94 | /// Moves the selected items to the top of the list.
95 | final bool separateSelectedItems;
96 |
97 | /// Style the text that is typed into the search field.
98 | final TextStyle? searchTextStyle;
99 |
100 | /// Style the search hint.
101 | final TextStyle? searchHintStyle;
102 |
103 | /// Set the color of the check in the checkbox
104 | final Color? checkColor;
105 |
106 | /// Whether the user can dismiss the widget by tapping outside
107 | final bool isDismissible;
108 |
109 | final AutovalidateMode autovalidateMode;
110 | final FormFieldValidator>? validator;
111 | final FormFieldSetter>? onSaved;
112 | final GlobalKey? key;
113 | FormFieldState>? state;
114 |
115 | MultiSelectBottomSheetField({
116 | required this.items,
117 | required this.onConfirm,
118 | this.title,
119 | this.buttonText,
120 | this.buttonIcon,
121 | this.listType,
122 | this.decoration,
123 | this.onSelectionChanged,
124 | this.chipDisplay,
125 | this.initialValue = const [],
126 | this.searchable = false,
127 | this.confirmText,
128 | this.cancelText,
129 | this.selectedColor,
130 | this.initialChildSize,
131 | this.minChildSize,
132 | this.maxChildSize,
133 | this.shape,
134 | this.barrierColor,
135 | this.searchHint,
136 | this.colorator,
137 | this.backgroundColor,
138 | this.unselectedColor,
139 | this.searchIcon,
140 | this.closeSearchIcon,
141 | this.itemsTextStyle,
142 | this.searchTextStyle,
143 | this.searchHintStyle,
144 | this.selectedItemsTextStyle,
145 | this.separateSelectedItems = false,
146 | this.checkColor,
147 | this.isDismissible = true,
148 | this.key,
149 | this.onSaved,
150 | this.validator,
151 | this.autovalidateMode = AutovalidateMode.disabled,
152 | }) : super(
153 | key: key,
154 | onSaved: onSaved,
155 | validator: validator,
156 | autovalidateMode: autovalidateMode,
157 | initialValue: initialValue,
158 | builder: (FormFieldState> state) {
159 | _MultiSelectBottomSheetFieldView view =
160 | _MultiSelectBottomSheetFieldView(
161 | items: items,
162 | decoration: decoration,
163 | unselectedColor: unselectedColor,
164 | colorator: colorator,
165 | itemsTextStyle: itemsTextStyle,
166 | selectedItemsTextStyle: selectedItemsTextStyle,
167 | backgroundColor: backgroundColor,
168 | title: title,
169 | initialValue: initialValue,
170 | barrierColor: barrierColor,
171 | buttonIcon: buttonIcon,
172 | buttonText: buttonText,
173 | cancelText: cancelText,
174 | chipDisplay: chipDisplay,
175 | closeSearchIcon: closeSearchIcon,
176 | confirmText: confirmText,
177 | initialChildSize: initialChildSize,
178 | listType: listType,
179 | maxChildSize: maxChildSize,
180 | minChildSize: minChildSize,
181 | onConfirm: onConfirm,
182 | onSelectionChanged: onSelectionChanged,
183 | searchHintStyle: searchHintStyle,
184 | searchIcon: searchIcon,
185 | searchHint: searchHint,
186 | searchTextStyle: searchTextStyle,
187 | searchable: searchable,
188 | selectedColor: selectedColor,
189 | separateSelectedItems: separateSelectedItems,
190 | shape: shape,
191 | checkColor: checkColor,
192 | isDismissible: isDismissible,
193 | );
194 | return _MultiSelectBottomSheetFieldView._withState(
195 | view as _MultiSelectBottomSheetFieldView, state);
196 | });
197 | }
198 |
199 | // ignore: must_be_immutable
200 | class _MultiSelectBottomSheetFieldView extends StatefulWidget {
201 | final BoxDecoration? decoration;
202 | final Text? buttonText;
203 | final Icon? buttonIcon;
204 | final List> items;
205 | final List initialValue;
206 | final Widget? title;
207 | final void Function(List)? onSelectionChanged;
208 | final void Function(List)? onConfirm;
209 | final bool searchable;
210 | final Text? confirmText;
211 | final Text? cancelText;
212 | final MultiSelectListType? listType;
213 | final Color? selectedColor;
214 | final String? searchHint;
215 | final double? initialChildSize;
216 | final double? minChildSize;
217 | final double? maxChildSize;
218 | final ShapeBorder? shape;
219 | final Color? barrierColor;
220 | final MultiSelectChipDisplay? chipDisplay;
221 | final Color Function(V)? colorator;
222 | final Color? backgroundColor;
223 | final Color? unselectedColor;
224 | final Icon? searchIcon;
225 | final Icon? closeSearchIcon;
226 | final TextStyle? itemsTextStyle;
227 | final TextStyle? selectedItemsTextStyle;
228 | final TextStyle? searchTextStyle;
229 | final TextStyle? searchHintStyle;
230 | final bool separateSelectedItems;
231 | final Color? checkColor;
232 | final bool isDismissible;
233 | FormFieldState>? state;
234 |
235 | _MultiSelectBottomSheetFieldView({
236 | required this.items,
237 | this.title,
238 | this.buttonText,
239 | this.buttonIcon,
240 | this.listType,
241 | this.decoration,
242 | this.onSelectionChanged,
243 | this.onConfirm,
244 | this.chipDisplay,
245 | required this.initialValue,
246 | required this.searchable,
247 | this.confirmText,
248 | this.cancelText,
249 | this.selectedColor,
250 | this.initialChildSize,
251 | this.minChildSize,
252 | this.maxChildSize,
253 | this.shape,
254 | this.barrierColor,
255 | this.searchHint,
256 | this.colorator,
257 | this.backgroundColor,
258 | this.unselectedColor,
259 | this.searchIcon,
260 | this.closeSearchIcon,
261 | this.itemsTextStyle,
262 | this.searchTextStyle,
263 | this.searchHintStyle,
264 | this.selectedItemsTextStyle,
265 | required this.separateSelectedItems,
266 | this.checkColor,
267 | required this.isDismissible,
268 | });
269 |
270 | /// This constructor allows a FormFieldState to be passed in. Called by MultiSelectBottomSheetField.
271 | _MultiSelectBottomSheetFieldView._withState(
272 | _MultiSelectBottomSheetFieldView field, FormFieldState> state)
273 | : items = field.items,
274 | title = field.title,
275 | buttonText = field.buttonText,
276 | buttonIcon = field.buttonIcon,
277 | listType = field.listType,
278 | decoration = field.decoration,
279 | onSelectionChanged = field.onSelectionChanged,
280 | onConfirm = field.onConfirm,
281 | chipDisplay = field.chipDisplay,
282 | initialValue = field.initialValue,
283 | searchable = field.searchable,
284 | confirmText = field.confirmText,
285 | cancelText = field.cancelText,
286 | selectedColor = field.selectedColor,
287 | initialChildSize = field.initialChildSize,
288 | minChildSize = field.minChildSize,
289 | maxChildSize = field.maxChildSize,
290 | shape = field.shape,
291 | barrierColor = field.barrierColor,
292 | searchHint = field.searchHint,
293 | colorator = field.colorator,
294 | backgroundColor = field.backgroundColor,
295 | unselectedColor = field.unselectedColor,
296 | searchIcon = field.searchIcon,
297 | closeSearchIcon = field.closeSearchIcon,
298 | itemsTextStyle = field.itemsTextStyle,
299 | searchHintStyle = field.searchHintStyle,
300 | searchTextStyle = field.searchTextStyle,
301 | selectedItemsTextStyle = field.selectedItemsTextStyle,
302 | separateSelectedItems = field.separateSelectedItems,
303 | checkColor = field.checkColor,
304 | isDismissible = field.isDismissible,
305 | state = state;
306 |
307 | @override
308 | __MultiSelectBottomSheetFieldViewState createState() =>
309 | __MultiSelectBottomSheetFieldViewState();
310 | }
311 |
312 | class __MultiSelectBottomSheetFieldViewState
313 | extends State<_MultiSelectBottomSheetFieldView> {
314 | List _selectedItems = [];
315 |
316 | @override
317 | void initState() {
318 | super.initState();
319 | _selectedItems.addAll(widget.initialValue);
320 | }
321 |
322 | @override
323 | void didUpdateWidget(_MultiSelectBottomSheetFieldView oldWidget) {
324 | super.didUpdateWidget(oldWidget);
325 |
326 | if (oldWidget.initialValue != widget.initialValue) {
327 | _selectedItems = [];
328 | _selectedItems.addAll(widget.initialValue);
329 |
330 | WidgetsBinding.instance.addPostFrameCallback((_) {
331 | widget.state!.didChange(_selectedItems);
332 | });
333 | }
334 | }
335 |
336 | Widget _buildInheritedChipDisplay() {
337 | List?> chipDisplayItems = [];
338 | chipDisplayItems = _selectedItems
339 | .map((e) =>
340 | widget.items.firstWhereOrNull((element) => e == element.value))
341 | .toList();
342 | chipDisplayItems.removeWhere((element) => element == null);
343 | if (widget.chipDisplay != null) {
344 | // if user has specified a chipDisplay, use its params
345 | if (widget.chipDisplay!.disabled!) {
346 | return Container();
347 | } else {
348 | return MultiSelectChipDisplay(
349 | items: chipDisplayItems,
350 | colorator: widget.chipDisplay!.colorator ?? widget.colorator,
351 | onTap: (item) {
352 | List? newValues;
353 | if (widget.chipDisplay!.onTap != null) {
354 | dynamic result = widget.chipDisplay!.onTap!(item);
355 | if (result is List) newValues = result;
356 | }
357 | if (newValues != null) {
358 | _selectedItems = newValues;
359 | if (widget.state != null) {
360 | widget.state!.didChange(_selectedItems);
361 | }
362 | }
363 | },
364 | decoration: widget.chipDisplay!.decoration,
365 | chipColor: widget.chipDisplay!.chipColor ??
366 | ((widget.selectedColor != null &&
367 | widget.selectedColor != Colors.transparent)
368 | ? widget.selectedColor!.withOpacity(0.35)
369 | : null),
370 | alignment: widget.chipDisplay!.alignment,
371 | textStyle: widget.chipDisplay!.textStyle,
372 | icon: widget.chipDisplay!.icon,
373 | shape: widget.chipDisplay!.shape,
374 | scroll: widget.chipDisplay!.scroll,
375 | scrollBar: widget.chipDisplay!.scrollBar,
376 | height: widget.chipDisplay!.height,
377 | chipWidth: widget.chipDisplay!.chipWidth,
378 | );
379 | }
380 | } else {
381 | // user didn't specify a chipDisplay, build the default
382 | return MultiSelectChipDisplay(
383 | items: chipDisplayItems,
384 | colorator: widget.colorator,
385 | chipColor: (widget.selectedColor != null &&
386 | widget.selectedColor != Colors.transparent)
387 | ? widget.selectedColor!.withOpacity(0.35)
388 | : null,
389 | );
390 | }
391 | }
392 |
393 | _showBottomSheet(BuildContext ctx) async {
394 | List? myVar = await showModalBottomSheet>(
395 | isDismissible: widget.isDismissible,
396 | backgroundColor: widget.backgroundColor,
397 | barrierColor: widget.barrierColor,
398 | shape: widget.shape ??
399 | RoundedRectangleBorder(
400 | borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)),
401 | ),
402 | isScrollControlled: true,
403 | context: context,
404 | builder: (context) {
405 | return MultiSelectBottomSheet(
406 | checkColor: widget.checkColor,
407 | selectedItemsTextStyle: widget.selectedItemsTextStyle,
408 | searchTextStyle: widget.searchTextStyle,
409 | searchHintStyle: widget.searchHintStyle,
410 | itemsTextStyle: widget.itemsTextStyle,
411 | searchIcon: widget.searchIcon,
412 | closeSearchIcon: widget.closeSearchIcon,
413 | unselectedColor: widget.unselectedColor,
414 | colorator: widget.colorator,
415 | searchHint: widget.searchHint,
416 | selectedColor: widget.selectedColor,
417 | listType: widget.listType,
418 | items: widget.items,
419 | cancelText: widget.cancelText,
420 | confirmText: widget.confirmText,
421 | separateSelectedItems: widget.separateSelectedItems,
422 | initialValue: _selectedItems,
423 | onConfirm: (selected) {
424 | if (widget.state != null) {
425 | widget.state!.didChange(selected);
426 | }
427 | _selectedItems = selected;
428 | if (widget.onConfirm != null) widget.onConfirm!(selected);
429 | },
430 | onSelectionChanged: widget.onSelectionChanged,
431 | searchable: widget.searchable,
432 | title: widget.title,
433 | initialChildSize: widget.initialChildSize,
434 | minChildSize: widget.minChildSize,
435 | maxChildSize: widget.maxChildSize,
436 | );
437 | });
438 | print(myVar.toString());
439 | _selectedItems = myVar!;
440 | }
441 |
442 | @override
443 | Widget build(BuildContext context) {
444 | return Column(
445 | mainAxisAlignment: MainAxisAlignment.start,
446 | children: [
447 | InkWell(
448 | onTap: () {
449 | _showBottomSheet(context);
450 | },
451 | child: Container(
452 | decoration: widget.state != null
453 | ? widget.decoration ??
454 | BoxDecoration(
455 | border: Border(
456 | bottom: BorderSide(
457 | color: widget.state != null && widget.state!.hasError
458 | ? Colors.red.shade800.withOpacity(0.6)
459 | : _selectedItems.isNotEmpty
460 | ? (widget.selectedColor != null &&
461 | widget.selectedColor !=
462 | Colors.transparent)
463 | ? widget.selectedColor!
464 | : Theme.of(context).primaryColor
465 | : Colors.black45,
466 | width: _selectedItems.isNotEmpty
467 | ? (widget.state != null && widget.state!.hasError)
468 | ? 1.4
469 | : 1.8
470 | : 1.2,
471 | ),
472 | ),
473 | )
474 | : widget.decoration,
475 | padding: EdgeInsets.all(10),
476 | child: Row(
477 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
478 | children: [
479 | widget.buttonText ?? Text("Select"),
480 | widget.buttonIcon ?? Icon(Icons.arrow_downward),
481 | ],
482 | ),
483 | ),
484 | ),
485 | _buildInheritedChipDisplay(),
486 | widget.state != null && widget.state!.hasError
487 | ? SizedBox(height: 5)
488 | : Container(),
489 | widget.state != null && widget.state!.hasError
490 | ? Row(
491 | children: [
492 | Padding(
493 | padding: const EdgeInsets.only(left: 4),
494 | child: Text(
495 | widget.state!.errorText!,
496 | style: TextStyle(
497 | color: Colors.red[800],
498 | fontSize: 12.5,
499 | ),
500 | ),
501 | ),
502 | ],
503 | )
504 | : Container(),
505 | ],
506 | );
507 | }
508 | }
509 |
--------------------------------------------------------------------------------
/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 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | );
26 | name = "Embed Frameworks";
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 9740EEB11CF90186004384FC /* Flutter */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
65 | );
66 | name = Flutter;
67 | sourceTree = "";
68 | };
69 | 97C146E51CF9000F007C117D = {
70 | isa = PBXGroup;
71 | children = (
72 | 9740EEB11CF90186004384FC /* Flutter */,
73 | 97C146F01CF9000F007C117D /* Runner */,
74 | 97C146EF1CF9000F007C117D /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 97C146EF1CF9000F007C117D /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 97C146EE1CF9000F007C117D /* Runner.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 97C146F01CF9000F007C117D /* Runner */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
92 | 97C147021CF9000F007C117D /* Info.plist */,
93 | 97C146F11CF9000F007C117D /* Supporting Files */,
94 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
95 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
96 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
97 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
98 | );
99 | path = Runner;
100 | sourceTree = "";
101 | };
102 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
103 | isa = PBXGroup;
104 | children = (
105 | );
106 | name = "Supporting Files";
107 | sourceTree = "";
108 | };
109 | /* End PBXGroup section */
110 |
111 | /* Begin PBXNativeTarget section */
112 | 97C146ED1CF9000F007C117D /* Runner */ = {
113 | isa = PBXNativeTarget;
114 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
115 | buildPhases = (
116 | 9740EEB61CF901F6004384FC /* Run Script */,
117 | 97C146EA1CF9000F007C117D /* Sources */,
118 | 97C146EB1CF9000F007C117D /* Frameworks */,
119 | 97C146EC1CF9000F007C117D /* Resources */,
120 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
121 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
122 | );
123 | buildRules = (
124 | );
125 | dependencies = (
126 | );
127 | name = Runner;
128 | productName = Runner;
129 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
130 | productType = "com.apple.product-type.application";
131 | };
132 | /* End PBXNativeTarget section */
133 |
134 | /* Begin PBXProject section */
135 | 97C146E61CF9000F007C117D /* Project object */ = {
136 | isa = PBXProject;
137 | attributes = {
138 | LastUpgradeCheck = 1020;
139 | ORGANIZATIONNAME = "";
140 | TargetAttributes = {
141 | 97C146ED1CF9000F007C117D = {
142 | CreatedOnToolsVersion = 7.3.1;
143 | LastSwiftMigration = 1100;
144 | };
145 | };
146 | };
147 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
148 | compatibilityVersion = "Xcode 9.3";
149 | developmentRegion = en;
150 | hasScannedForEncodings = 0;
151 | knownRegions = (
152 | en,
153 | Base,
154 | );
155 | mainGroup = 97C146E51CF9000F007C117D;
156 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
157 | projectDirPath = "";
158 | projectRoot = "";
159 | targets = (
160 | 97C146ED1CF9000F007C117D /* Runner */,
161 | );
162 | };
163 | /* End PBXProject section */
164 |
165 | /* Begin PBXResourcesBuildPhase section */
166 | 97C146EC1CF9000F007C117D /* Resources */ = {
167 | isa = PBXResourcesBuildPhase;
168 | buildActionMask = 2147483647;
169 | files = (
170 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
171 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
172 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
173 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
174 | );
175 | runOnlyForDeploymentPostprocessing = 0;
176 | };
177 | /* End PBXResourcesBuildPhase section */
178 |
179 | /* Begin PBXShellScriptBuildPhase section */
180 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
181 | isa = PBXShellScriptBuildPhase;
182 | buildActionMask = 2147483647;
183 | files = (
184 | );
185 | inputPaths = (
186 | );
187 | name = "Thin Binary";
188 | outputPaths = (
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | shellPath = /bin/sh;
192 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
193 | };
194 | 9740EEB61CF901F6004384FC /* Run Script */ = {
195 | isa = PBXShellScriptBuildPhase;
196 | buildActionMask = 2147483647;
197 | files = (
198 | );
199 | inputPaths = (
200 | );
201 | name = "Run Script";
202 | outputPaths = (
203 | );
204 | runOnlyForDeploymentPostprocessing = 0;
205 | shellPath = /bin/sh;
206 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
207 | };
208 | /* End PBXShellScriptBuildPhase section */
209 |
210 | /* Begin PBXSourcesBuildPhase section */
211 | 97C146EA1CF9000F007C117D /* Sources */ = {
212 | isa = PBXSourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
216 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
217 | );
218 | runOnlyForDeploymentPostprocessing = 0;
219 | };
220 | /* End PBXSourcesBuildPhase section */
221 |
222 | /* Begin PBXVariantGroup section */
223 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
224 | isa = PBXVariantGroup;
225 | children = (
226 | 97C146FB1CF9000F007C117D /* Base */,
227 | );
228 | name = Main.storyboard;
229 | sourceTree = "";
230 | };
231 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
232 | isa = PBXVariantGroup;
233 | children = (
234 | 97C147001CF9000F007C117D /* Base */,
235 | );
236 | name = LaunchScreen.storyboard;
237 | sourceTree = "";
238 | };
239 | /* End PBXVariantGroup section */
240 |
241 | /* Begin XCBuildConfiguration section */
242 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
243 | isa = XCBuildConfiguration;
244 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
245 | buildSettings = {
246 | ALWAYS_SEARCH_USER_PATHS = NO;
247 | CLANG_ANALYZER_NONNULL = YES;
248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
249 | CLANG_CXX_LIBRARY = "libc++";
250 | CLANG_ENABLE_MODULES = YES;
251 | CLANG_ENABLE_OBJC_ARC = YES;
252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
253 | CLANG_WARN_BOOL_CONVERSION = YES;
254 | CLANG_WARN_COMMA = YES;
255 | CLANG_WARN_CONSTANT_CONVERSION = YES;
256 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
258 | CLANG_WARN_EMPTY_BODY = YES;
259 | CLANG_WARN_ENUM_CONVERSION = YES;
260 | CLANG_WARN_INFINITE_RECURSION = YES;
261 | CLANG_WARN_INT_CONVERSION = YES;
262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
267 | CLANG_WARN_STRICT_PROTOTYPES = YES;
268 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
269 | CLANG_WARN_UNREACHABLE_CODE = YES;
270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
271 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
272 | COPY_PHASE_STRIP = NO;
273 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
274 | ENABLE_NS_ASSERTIONS = NO;
275 | ENABLE_STRICT_OBJC_MSGSEND = YES;
276 | GCC_C_LANGUAGE_STANDARD = gnu99;
277 | GCC_NO_COMMON_BLOCKS = YES;
278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
280 | GCC_WARN_UNDECLARED_SELECTOR = YES;
281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
282 | GCC_WARN_UNUSED_FUNCTION = YES;
283 | GCC_WARN_UNUSED_VARIABLE = YES;
284 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
285 | MTL_ENABLE_DEBUG_INFO = NO;
286 | SDKROOT = iphoneos;
287 | SUPPORTED_PLATFORMS = iphoneos;
288 | TARGETED_DEVICE_FAMILY = "1,2";
289 | VALIDATE_PRODUCT = YES;
290 | };
291 | name = Profile;
292 | };
293 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
294 | isa = XCBuildConfiguration;
295 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
296 | buildSettings = {
297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
298 | CLANG_ENABLE_MODULES = YES;
299 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
300 | ENABLE_BITCODE = NO;
301 | FRAMEWORK_SEARCH_PATHS = (
302 | "$(inherited)",
303 | "$(PROJECT_DIR)/Flutter",
304 | );
305 | INFOPLIST_FILE = Runner/Info.plist;
306 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
307 | LIBRARY_SEARCH_PATHS = (
308 | "$(inherited)",
309 | "$(PROJECT_DIR)/Flutter",
310 | );
311 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
312 | PRODUCT_NAME = "$(TARGET_NAME)";
313 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
314 | SWIFT_VERSION = 5.0;
315 | VERSIONING_SYSTEM = "apple-generic";
316 | };
317 | name = Profile;
318 | };
319 | 97C147031CF9000F007C117D /* Debug */ = {
320 | isa = XCBuildConfiguration;
321 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
322 | buildSettings = {
323 | ALWAYS_SEARCH_USER_PATHS = NO;
324 | CLANG_ANALYZER_NONNULL = YES;
325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
326 | CLANG_CXX_LIBRARY = "libc++";
327 | CLANG_ENABLE_MODULES = YES;
328 | CLANG_ENABLE_OBJC_ARC = YES;
329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
330 | CLANG_WARN_BOOL_CONVERSION = YES;
331 | CLANG_WARN_COMMA = YES;
332 | CLANG_WARN_CONSTANT_CONVERSION = YES;
333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
335 | CLANG_WARN_EMPTY_BODY = YES;
336 | CLANG_WARN_ENUM_CONVERSION = YES;
337 | CLANG_WARN_INFINITE_RECURSION = YES;
338 | CLANG_WARN_INT_CONVERSION = YES;
339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
344 | CLANG_WARN_STRICT_PROTOTYPES = YES;
345 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
346 | CLANG_WARN_UNREACHABLE_CODE = YES;
347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
349 | COPY_PHASE_STRIP = NO;
350 | DEBUG_INFORMATION_FORMAT = dwarf;
351 | ENABLE_STRICT_OBJC_MSGSEND = YES;
352 | ENABLE_TESTABILITY = YES;
353 | GCC_C_LANGUAGE_STANDARD = gnu99;
354 | GCC_DYNAMIC_NO_PIC = NO;
355 | GCC_NO_COMMON_BLOCKS = YES;
356 | GCC_OPTIMIZATION_LEVEL = 0;
357 | GCC_PREPROCESSOR_DEFINITIONS = (
358 | "DEBUG=1",
359 | "$(inherited)",
360 | );
361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
363 | GCC_WARN_UNDECLARED_SELECTOR = YES;
364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
365 | GCC_WARN_UNUSED_FUNCTION = YES;
366 | GCC_WARN_UNUSED_VARIABLE = YES;
367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
368 | MTL_ENABLE_DEBUG_INFO = YES;
369 | ONLY_ACTIVE_ARCH = YES;
370 | SDKROOT = iphoneos;
371 | TARGETED_DEVICE_FAMILY = "1,2";
372 | };
373 | name = Debug;
374 | };
375 | 97C147041CF9000F007C117D /* Release */ = {
376 | isa = XCBuildConfiguration;
377 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
378 | buildSettings = {
379 | ALWAYS_SEARCH_USER_PATHS = NO;
380 | CLANG_ANALYZER_NONNULL = YES;
381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
382 | CLANG_CXX_LIBRARY = "libc++";
383 | CLANG_ENABLE_MODULES = YES;
384 | CLANG_ENABLE_OBJC_ARC = YES;
385 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
386 | CLANG_WARN_BOOL_CONVERSION = YES;
387 | CLANG_WARN_COMMA = YES;
388 | CLANG_WARN_CONSTANT_CONVERSION = YES;
389 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
391 | CLANG_WARN_EMPTY_BODY = YES;
392 | CLANG_WARN_ENUM_CONVERSION = YES;
393 | CLANG_WARN_INFINITE_RECURSION = YES;
394 | CLANG_WARN_INT_CONVERSION = YES;
395 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
396 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
397 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
398 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
399 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
400 | CLANG_WARN_STRICT_PROTOTYPES = YES;
401 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
402 | CLANG_WARN_UNREACHABLE_CODE = YES;
403 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
404 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
405 | COPY_PHASE_STRIP = NO;
406 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
407 | ENABLE_NS_ASSERTIONS = NO;
408 | ENABLE_STRICT_OBJC_MSGSEND = YES;
409 | GCC_C_LANGUAGE_STANDARD = gnu99;
410 | GCC_NO_COMMON_BLOCKS = YES;
411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
413 | GCC_WARN_UNDECLARED_SELECTOR = YES;
414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
415 | GCC_WARN_UNUSED_FUNCTION = YES;
416 | GCC_WARN_UNUSED_VARIABLE = YES;
417 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
418 | MTL_ENABLE_DEBUG_INFO = NO;
419 | SDKROOT = iphoneos;
420 | SUPPORTED_PLATFORMS = iphoneos;
421 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
422 | TARGETED_DEVICE_FAMILY = "1,2";
423 | VALIDATE_PRODUCT = YES;
424 | };
425 | name = Release;
426 | };
427 | 97C147061CF9000F007C117D /* Debug */ = {
428 | isa = XCBuildConfiguration;
429 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
430 | buildSettings = {
431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
432 | CLANG_ENABLE_MODULES = YES;
433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
434 | ENABLE_BITCODE = NO;
435 | FRAMEWORK_SEARCH_PATHS = (
436 | "$(inherited)",
437 | "$(PROJECT_DIR)/Flutter",
438 | );
439 | INFOPLIST_FILE = Runner/Info.plist;
440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
441 | LIBRARY_SEARCH_PATHS = (
442 | "$(inherited)",
443 | "$(PROJECT_DIR)/Flutter",
444 | );
445 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
446 | PRODUCT_NAME = "$(TARGET_NAME)";
447 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
448 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
449 | SWIFT_VERSION = 5.0;
450 | VERSIONING_SYSTEM = "apple-generic";
451 | };
452 | name = Debug;
453 | };
454 | 97C147071CF9000F007C117D /* Release */ = {
455 | isa = XCBuildConfiguration;
456 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
457 | buildSettings = {
458 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
459 | CLANG_ENABLE_MODULES = YES;
460 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
461 | ENABLE_BITCODE = NO;
462 | FRAMEWORK_SEARCH_PATHS = (
463 | "$(inherited)",
464 | "$(PROJECT_DIR)/Flutter",
465 | );
466 | INFOPLIST_FILE = Runner/Info.plist;
467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
468 | LIBRARY_SEARCH_PATHS = (
469 | "$(inherited)",
470 | "$(PROJECT_DIR)/Flutter",
471 | );
472 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
473 | PRODUCT_NAME = "$(TARGET_NAME)";
474 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
475 | SWIFT_VERSION = 5.0;
476 | VERSIONING_SYSTEM = "apple-generic";
477 | };
478 | name = Release;
479 | };
480 | /* End XCBuildConfiguration section */
481 |
482 | /* Begin XCConfigurationList section */
483 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
484 | isa = XCConfigurationList;
485 | buildConfigurations = (
486 | 97C147031CF9000F007C117D /* Debug */,
487 | 97C147041CF9000F007C117D /* Release */,
488 | 249021D3217E4FDB00AE95B9 /* Profile */,
489 | );
490 | defaultConfigurationIsVisible = 0;
491 | defaultConfigurationName = Release;
492 | };
493 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
494 | isa = XCConfigurationList;
495 | buildConfigurations = (
496 | 97C147061CF9000F007C117D /* Debug */,
497 | 97C147071CF9000F007C117D /* Release */,
498 | 249021D4217E4FDB00AE95B9 /* Profile */,
499 | );
500 | defaultConfigurationIsVisible = 0;
501 | defaultConfigurationName = Release;
502 | };
503 | /* End XCConfigurationList section */
504 | };
505 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
506 | }
507 |
--------------------------------------------------------------------------------
/lib/chip_field/multi_select_chip_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../multi_select_flutter.dart';
3 |
4 | class MultiSelectChipField extends FormField> {
5 | /// Style the Container that makes up the field.
6 | final BoxDecoration? decoration;
7 |
8 | /// List of items to select from.
9 | final List> items;
10 |
11 | /// Color of the chip while not selected.
12 | final Color? chipColor;
13 |
14 | /// Sets the color of the chip while selected.
15 | final Color? selectedChipColor;
16 |
17 | /// Style the text of the chips.
18 | final TextStyle? textStyle;
19 |
20 | /// Style the text of the selected chips.
21 | final TextStyle? selectedTextStyle;
22 |
23 | /// The icon displayed in front of text on selected chips.
24 | final Icon? icon;
25 |
26 | /// Replaces the deafult search icon when searchable is true.
27 | final Icon? searchIcon;
28 |
29 | /// Replaces the default close search icon when searchable is true.
30 | final Icon? closeSearchIcon;
31 |
32 | /// Set a ShapeBorder. Typically a RoundedRectangularBorder.
33 | final ShapeBorder? chipShape;
34 |
35 | /// Defines the header text.
36 | final Text? title;
37 |
38 | /// Enables horizontal scrolling. Default is true.
39 | final bool scroll;
40 |
41 | /// A function that sets the color of selected items based on their value.
42 | final Color Function(V)? colorator;
43 |
44 | /// Fires when a chip is tapped. A good time to store the selected values.
45 | final Function(List)? onTap;
46 |
47 | /// Enables search functionality.
48 | final bool? searchable;
49 |
50 | /// Set the search hint.
51 | final String? searchHint;
52 |
53 | /// Set the TextStyle of the search hint.
54 | final TextStyle? searchHintStyle;
55 |
56 | /// Set the TextStyle of the text that gets typed into the search bar.
57 | final TextStyle? searchTextStyle;
58 |
59 | /// Set the header color.
60 | final Color? headerColor;
61 |
62 | /// Build a custom widget that gets created dynamically for each item.
63 | final Widget Function(MultiSelectItem, FormFieldState>)?
64 | itemBuilder;
65 |
66 | /// Set the height of the selectable area.
67 | final double? height;
68 |
69 | /// Make use of the ScrollController to automatically scroll through the list.
70 | final Function(ScrollController)? scrollControl;
71 |
72 | /// Define a HorizontalScrollBar.
73 | final HorizontalScrollBar? scrollBar;
74 |
75 | /// Determines whether to show the header.
76 | final bool showHeader;
77 |
78 | /// Set the width of the chip.
79 | final double? chipWidth;
80 |
81 | final List initialValue;
82 | final AutovalidateMode autovalidateMode;
83 | final FormFieldValidator>? validator;
84 | final FormFieldSetter>? onSaved;
85 | final GlobalKey? key;
86 |
87 | MultiSelectChipField({
88 | required this.items,
89 | this.decoration,
90 | this.chipColor,
91 | this.selectedChipColor,
92 | this.colorator,
93 | this.textStyle,
94 | this.selectedTextStyle,
95 | this.icon,
96 | this.searchIcon,
97 | this.closeSearchIcon,
98 | this.chipShape,
99 | this.onTap,
100 | this.title,
101 | this.scroll = true,
102 | this.searchable,
103 | this.searchHint,
104 | this.searchHintStyle,
105 | this.searchTextStyle,
106 | this.headerColor,
107 | this.key,
108 | this.onSaved,
109 | this.validator,
110 | this.autovalidateMode = AutovalidateMode.disabled,
111 | this.initialValue = const [],
112 | this.itemBuilder,
113 | this.height,
114 | this.scrollControl,
115 | this.scrollBar,
116 | this.showHeader = true,
117 | this.chipWidth,
118 | }) : super(
119 | key: key,
120 | onSaved: onSaved,
121 | validator: validator,
122 | autovalidateMode: autovalidateMode,
123 | initialValue: initialValue,
124 | builder: (FormFieldState> state) {
125 | _MultiSelectChipFieldView view = _MultiSelectChipFieldView(
126 | items: items,
127 | decoration: decoration,
128 | chipColor: chipColor,
129 | selectedChipColor: selectedChipColor,
130 | colorator: colorator,
131 | textStyle: textStyle,
132 | selectedTextStyle: selectedTextStyle,
133 | icon: icon,
134 | searchIcon: searchIcon,
135 | closeSearchIcon: closeSearchIcon,
136 | chipShape: chipShape,
137 | onTap: onTap,
138 | title: title,
139 | scroll: scroll,
140 | initialValue: initialValue,
141 | searchable: searchable,
142 | searchHint: searchHint,
143 | searchHintStyle: searchHintStyle,
144 | searchTextStyle: searchTextStyle,
145 | headerColor: headerColor,
146 | itemBuilder: itemBuilder,
147 | height: height,
148 | scrollControl: scrollControl,
149 | scrollBar: scrollBar,
150 | showHeader: showHeader,
151 | chipWidth: chipWidth,
152 | );
153 | return _MultiSelectChipFieldView.withState(
154 | view as _MultiSelectChipFieldView, state);
155 | });
156 | }
157 |
158 | // ignore: must_be_immutable
159 | class _MultiSelectChipFieldView extends StatefulWidget
160 | with MultiSelectActions {
161 | final BoxDecoration? decoration;
162 | final List> items;
163 | final List>? selectedItems;
164 | final Color? chipColor;
165 | final Color? selectedChipColor;
166 | final TextStyle? textStyle;
167 | final TextStyle? selectedTextStyle;
168 | final Icon? icon;
169 | final Icon? searchIcon;
170 | final Icon? closeSearchIcon;
171 | final ShapeBorder? chipShape;
172 | final Text? title;
173 | final bool scroll;
174 | final bool? searchable;
175 | final String? searchHint;
176 | final TextStyle? searchHintStyle;
177 | final TextStyle? searchTextStyle;
178 | final List initialValue;
179 | final Color? Function(V)? colorator;
180 | final Function(List)? onTap;
181 | final Color? headerColor;
182 | final Widget Function(MultiSelectItem, FormFieldState>)?
183 | itemBuilder;
184 | final double? height;
185 | FormFieldState>? state;
186 | final Function(ScrollController)? scrollControl;
187 | final HorizontalScrollBar? scrollBar;
188 | final bool showHeader;
189 | final double? chipWidth;
190 |
191 | _MultiSelectChipFieldView({
192 | required this.items,
193 | this.selectedItems,
194 | this.decoration,
195 | this.chipColor,
196 | this.selectedChipColor,
197 | this.colorator,
198 | this.textStyle,
199 | this.selectedTextStyle,
200 | this.icon,
201 | this.chipShape,
202 | this.onTap,
203 | this.title,
204 | this.scroll = true,
205 | this.initialValue = const [],
206 | this.searchable,
207 | this.searchHint,
208 | this.searchIcon,
209 | this.closeSearchIcon,
210 | this.searchHintStyle,
211 | this.searchTextStyle,
212 | this.headerColor,
213 | this.itemBuilder,
214 | this.height,
215 | this.scrollControl,
216 | this.scrollBar,
217 | this.showHeader = true,
218 | this.chipWidth,
219 | });
220 |
221 | /// This constructor allows a FormFieldState to be passed in. Called by MultiSelectChipField.
222 | _MultiSelectChipFieldView.withState(
223 | _MultiSelectChipFieldView field, FormFieldState> state)
224 | : items = field.items,
225 | title = field.title,
226 | decoration = field.decoration,
227 | initialValue = field.initialValue,
228 | selectedChipColor = field.selectedChipColor,
229 | chipShape = field.chipShape,
230 | colorator = field.colorator,
231 | chipColor = field.chipColor,
232 | icon = field.icon,
233 | closeSearchIcon = field.closeSearchIcon,
234 | selectedItems = field.selectedItems,
235 | textStyle = field.textStyle,
236 | scroll = field.scroll,
237 | selectedTextStyle = field.selectedTextStyle,
238 | onTap = field.onTap,
239 | searchable = field.searchable,
240 | searchHint = field.searchHint,
241 | searchIcon = field.searchIcon,
242 | searchTextStyle = field.searchTextStyle,
243 | searchHintStyle = field.searchHintStyle,
244 | headerColor = field.headerColor,
245 | itemBuilder = field.itemBuilder,
246 | height = field.height,
247 | scrollControl = field.scrollControl,
248 | scrollBar = field.scrollBar,
249 | showHeader = field.showHeader,
250 | chipWidth = field.chipWidth,
251 | state = state;
252 |
253 | @override
254 | __MultiSelectChipFieldViewState createState() =>
255 | __MultiSelectChipFieldViewState(items);
256 | }
257 |
258 | class __MultiSelectChipFieldViewState
259 | extends State<_MultiSelectChipFieldView> {
260 | List _selectedValues = [];
261 | bool _showSearch = false;
262 | List _items;
263 | ScrollController _scrollController = ScrollController();
264 |
265 | __MultiSelectChipFieldViewState(this._items);
266 |
267 | void initState() {
268 | super.initState();
269 | _selectedValues.addAll(widget.initialValue);
270 |
271 | if (widget.scrollControl != null && widget.scroll)
272 | WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToPosition());
273 | }
274 |
275 | @override
276 | void didUpdateWidget(_MultiSelectChipFieldView oldWidget) {
277 | super.didUpdateWidget(oldWidget);
278 |
279 | if (oldWidget.initialValue != widget.initialValue) {
280 | _selectedValues = [];
281 | _selectedValues.addAll(widget.initialValue);
282 |
283 | WidgetsBinding.instance.addPostFrameCallback((_) {
284 | widget.state!.didChange(_selectedValues);
285 | });
286 | }
287 | if (oldWidget.items != widget.items) {
288 | _items = [...widget.items];
289 | }
290 | }
291 |
292 | _scrollToPosition() {
293 | widget.scrollControl!(_scrollController);
294 | }
295 |
296 | @override
297 | Widget build(BuildContext context) {
298 | return Column(
299 | children: [
300 | Container(
301 | decoration: widget.decoration ??
302 | BoxDecoration(
303 | border:
304 | Border.all(width: 1, color: Theme.of(context).primaryColor),
305 | ),
306 | child: Column(
307 | children: [
308 | widget.showHeader
309 | ? Container(
310 | color:
311 | widget.headerColor ?? Theme.of(context).primaryColor,
312 | child: Row(
313 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
314 | children: [
315 | _showSearch
316 | ? Expanded(
317 | child: Container(
318 | padding: EdgeInsets.only(left: 10),
319 | child: TextField(
320 | style: widget.searchTextStyle,
321 | decoration: InputDecoration(
322 | hintStyle: widget.searchHintStyle,
323 | hintText: widget.searchHint ?? "Search",
324 | focusedBorder: UnderlineInputBorder(
325 | borderSide: BorderSide(
326 | color: widget.selectedChipColor ??
327 | Theme.of(context).primaryColor,
328 | ),
329 | ),
330 | ),
331 | onChanged: (val) {
332 | setState(() {
333 | _items = widget.updateSearchQuery(
334 | val, widget.items);
335 | });
336 | },
337 | ),
338 | ),
339 | )
340 | : Padding(
341 | padding: const EdgeInsets.only(left: 8.0),
342 | child: widget.title != null
343 | ? Text(
344 | widget.title!.data!,
345 | style: TextStyle(
346 | color: widget.title!.style != null
347 | ? widget.title!.style!.color
348 | : null,
349 | fontSize:
350 | widget.title!.style != null
351 | ? widget.title!.style!
352 | .fontSize ??
353 | 18
354 | : 18),
355 | )
356 | : Text(
357 | "Select",
358 | style: TextStyle(fontSize: 18),
359 | ),
360 | ),
361 | widget.searchable != null && widget.searchable!
362 | ? IconButton(
363 | icon: _showSearch
364 | ? widget.closeSearchIcon ??
365 | Icon(
366 | Icons.close,
367 | size: 22,
368 | )
369 | : widget.searchIcon ??
370 | Icon(
371 | Icons.search,
372 | size: 22,
373 | ),
374 | onPressed: () {
375 | setState(() {
376 | _showSearch = !_showSearch;
377 | if (!_showSearch) _items = widget.items;
378 | });
379 | },
380 | )
381 | : Padding(
382 | padding: EdgeInsets.all(18),
383 | ),
384 | ],
385 | ),
386 | )
387 | : Container(),
388 | widget.scroll
389 | ? Container(
390 | padding: widget.itemBuilder == null
391 | ? EdgeInsets.symmetric(horizontal: 5)
392 | : null,
393 | width: MediaQuery.of(context).size.width,
394 | height: widget.height ??
395 | MediaQuery.of(context).size.height * 0.08,
396 | child: widget.scrollBar != null
397 | ? Scrollbar(
398 | thumbVisibility: widget.scrollBar!.isAlwaysShown,
399 | controller: _scrollController,
400 | child: ListView.builder(
401 | controller: _scrollController,
402 | scrollDirection: Axis.horizontal,
403 | itemCount: _items.length,
404 | itemBuilder: (ctx, index) {
405 | return widget.itemBuilder != null
406 | ? widget.itemBuilder!(
407 | _items[index] as MultiSelectItem,
408 | widget.state!)
409 | : _buildItem(
410 | _items[index] as MultiSelectItem);
411 | },
412 | ),
413 | )
414 | : ListView.builder(
415 | controller: _scrollController,
416 | scrollDirection: Axis.horizontal,
417 | itemCount: _items.length,
418 | itemBuilder: (ctx, index) {
419 | return widget.itemBuilder != null
420 | ? widget.itemBuilder!(
421 | _items[index] as MultiSelectItem,
422 | widget.state!)
423 | : _buildItem(
424 | _items[index] as MultiSelectItem);
425 | },
426 | ),
427 | )
428 | : Container(
429 | height: widget.height,
430 | alignment: Alignment.centerLeft,
431 | padding: EdgeInsets.symmetric(horizontal: 10),
432 | child: Wrap(
433 | children: widget.itemBuilder != null
434 | ? _items
435 | .map((item) => widget.itemBuilder!(
436 | item as MultiSelectItem, widget.state!))
437 | .toList()
438 | : _items
439 | .map((item) =>
440 | _buildItem(item as MultiSelectItem))
441 | .toList(),
442 | ),
443 | ),
444 | ],
445 | ),
446 | ),
447 | widget.state != null && widget.state!.hasError
448 | ? Row(
449 | children: [
450 | Padding(
451 | padding: const EdgeInsets.fromLTRB(4, 4, 0, 0),
452 | child: Text(
453 | widget.state!.errorText!,
454 | style: TextStyle(
455 | color: Colors.red[800],
456 | fontSize: 12.5,
457 | ),
458 | ),
459 | ),
460 | ],
461 | )
462 | : Container(),
463 | ],
464 | );
465 | }
466 |
467 | Widget _buildItem(MultiSelectItem item) {
468 | return Container(
469 | margin: EdgeInsets.all(0),
470 | padding: const EdgeInsets.all(2.0),
471 | child: ChoiceChip(
472 | shape: widget.chipShape as OutlinedBorder? ??
473 | RoundedRectangleBorder(
474 | side: BorderSide(
475 | color: widget.colorator != null &&
476 | widget.colorator!(item.value) != null &&
477 | _selectedValues.contains(item.value)
478 | ? widget.colorator!(item.value)!
479 | : widget.selectedChipColor ??
480 | Theme.of(context).primaryColor),
481 | borderRadius: BorderRadius.vertical(
482 | top: Radius.circular(15.0),
483 | bottom: Radius.circular(15.0),
484 | ),
485 | ),
486 | avatar: _selectedValues.contains(item.value)
487 | ? widget.icon != null
488 | ? Icon(
489 | widget.icon!.icon,
490 | color: widget.colorator != null &&
491 | widget.colorator!(item.value) != null
492 | ? widget.colorator!(item.value)!.withOpacity(1)
493 | : widget.icon!.color ??
494 | widget.selectedChipColor ??
495 | Theme.of(context).primaryColor,
496 | )
497 | : null
498 | : null,
499 | label: Container(
500 | width: widget.chipWidth,
501 | child: Text(
502 | item.label,
503 | overflow: TextOverflow.ellipsis,
504 | style: _selectedValues.contains(item.value)
505 | ? TextStyle(
506 | color: widget.colorator != null &&
507 | widget.colorator!(item.value) != null
508 | ? widget.colorator!(item.value)!.withOpacity(1)
509 | : widget.selectedTextStyle != null
510 | ? widget.selectedTextStyle!.color
511 | : null)
512 | : TextStyle(
513 | color: widget.textStyle != null
514 | ? widget.textStyle!.color ?? widget.chipColor
515 | : widget.chipColor,
516 | fontSize: widget.textStyle != null
517 | ? widget.textStyle!.fontSize
518 | : null,
519 | ),
520 | ),
521 | ),
522 | selected: _selectedValues.contains(item.value),
523 | backgroundColor: widget.chipColor ?? Colors.white70,
524 | selectedColor:
525 | widget.colorator != null && widget.colorator!(item.value) != null
526 | ? widget.colorator!(item.value)
527 | : widget.selectedChipColor != null
528 | ? widget.selectedChipColor
529 | : Theme.of(context).primaryColor.withOpacity(0.33),
530 | onSelected: (_) {
531 | if (_) {
532 | _selectedValues.add(item.value);
533 | if (widget.state != null) {
534 | widget.state!.didChange(_selectedValues);
535 | }
536 | } else {
537 | _selectedValues.remove(item.value);
538 | if (widget.state != null) {
539 | widget.state!.didChange(_selectedValues);
540 | }
541 | }
542 | if (widget.onTap != null) widget.onTap!(_selectedValues);
543 | },
544 | ),
545 | );
546 | }
547 | }
548 |
--------------------------------------------------------------------------------