├── .cirrus.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── analysis_options.yaml ├── doc ├── .vuepress │ └── config.js ├── README.md ├── code │ └── custom_rule.dart ├── deploy.sh ├── each_rule.jpg ├── flrx_validator.png ├── package.json ├── rule.md ├── validator.md └── yarn.lock ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── cupertino_form.dart │ ├── main.dart │ └── material_form.dart ├── linux │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements ├── pubspec.yaml ├── test │ └── widget_test.dart ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ └── Icon-512.png │ ├── index.html │ └── manifest.json └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── run_loop.cpp │ ├── run_loop.h │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── lib ├── flrx_validator.dart └── src │ ├── rules │ ├── any_rule.dart │ ├── each_rule.dart │ ├── email_rule.dart │ ├── in_rule.dart │ ├── max_length_rule.dart │ ├── max_rule.dart │ ├── min_length_rule.dart │ ├── min_rule.dart │ ├── not_in_rule.dart │ ├── number_rule.dart │ ├── regex_rule.dart │ ├── required_rule.dart │ └── rule.dart │ ├── utils │ └── string_utils.dart │ └── validator.dart ├── pubspec.yaml └── test ├── mocks ├── mock_validator.dart └── mock_validator_rule.dart └── unit ├── rules ├── any_rule_test.dart ├── each_rule_test.dart ├── email_rule_test.dart ├── in_rule_test.dart ├── max_length_rule_test.dart ├── max_rule_test.dart ├── min_length_rule_test.dart ├── min_rule_test.dart ├── not_in_rule_test.dart ├── num_rule_test.dart ├── required_rule_test.dart └── rule_test.dart ├── utils └── string_utils_test.dart └── validator └── validator_test.dart /.cirrus.yml: -------------------------------------------------------------------------------- 1 | container: 2 | image: cirrusci/flutter:latest 3 | 4 | style_task: 5 | test_style_script: 6 | - dart format --set-exit-if-changed lib 7 | 8 | test_task: 9 | env: 10 | CODECOV_TOKEN: ENCRYPTED[0b39ebc2857b16573838848a4359338b0b198c8342d9f846182ef1bd81e810c43809b7af330ab8a49f7bd708ecb82663] 11 | pub_cache: 12 | folder: ~/.pub-cache 13 | test_script: 14 | - pub get 15 | - dart test --coverage=coverage 16 | codecov_script: 17 | - bash <(curl -s https://codecov.io/bash) -t $CODE_COV_TOKEN 18 | 19 | publish_task: 20 | only_if: $CIRRUS_BRANCH == 'master' 21 | depends_on: 22 | - test 23 | container: 24 | image: node:latest 25 | env: 26 | DEPLOYMENT_TOKEN: ENCRYPTED[892b4e38b52b3ffdf1ebf94761754ae05d32e23bfaf188baba0c651f8152f34dfc310f597a359086f55374387d8b72e8] 27 | node_modules_cache: 28 | folder: doc/node_modules 29 | populate_script: 30 | - cd doc 31 | - yarn install 32 | deploy_docs_script: 33 | - cd doc 34 | - yarn docs:build 35 | - ./deploy.sh -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | coverage/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | .dart_tool/ 27 | .flutter-plugins 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | pubspec.lock 33 | test/.test_coverage.dart 34 | coverage_badge.svg 35 | 36 | # Android related 37 | **/android/**/gradle-wrapper.jar 38 | **/android/.gradle 39 | **/android/captures/ 40 | **/android/gradlew 41 | **/android/gradlew.bat 42 | **/android/local.properties 43 | **/android/**/GeneratedPluginRegistrant.java 44 | 45 | # iOS/XCode related 46 | **/ios/**/*.mode1v3 47 | **/ios/**/*.mode2v3 48 | **/ios/**/*.moved-aside 49 | **/ios/**/*.pbxuser 50 | **/ios/**/*.perspectivev3 51 | **/ios/**/*sync/ 52 | **/ios/**/.sconsign.dblite 53 | **/ios/**/.tags* 54 | **/ios/**/.vagrant/ 55 | **/ios/**/DerivedData/ 56 | **/ios/**/Icon? 57 | **/ios/**/Pods/ 58 | **/ios/**/.symlinks/ 59 | **/ios/**/profile 60 | **/ios/**/xcuserdata 61 | **/ios/.generated/ 62 | **/ios/Flutter/App.framework 63 | **/ios/Flutter/Flutter.framework 64 | **/ios/Flutter/Generated.xcconfig 65 | **/ios/Flutter/app.flx 66 | **/ios/Flutter/app.zip 67 | **/ios/Flutter/flutter_assets/ 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 77 | 78 | # Vuepress related 79 | doc/.vuepress/dist/ 80 | doc/node_modules/ -------------------------------------------------------------------------------- /.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: 20e59316b8b8474554b38493b8ca888794b0234a 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [v0.6.0] 2 | ### Changed 3 | Migrated to Null Safety 4 | 5 | ## [v0.5.0] 6 | ### Added 7 | Added `NumberRule` which checks if the value is a valid number or not 8 | Added `MinRule` which checks if the value is greater a specific number or not 9 | Added `MaxRule` which checks if the value is less than a specific number or not 10 | 11 | ### Changed 12 | `MinLengthRule`, `MaxLengthRule` now supports List and Map 13 | `RequiredRule` now supports List, Map and other Data types. In case of other Data type, nullability of the value is tested 14 | Update Project Style 15 | Migrate Example to AndroidX 16 | Update Docs Dependencies 17 | 18 | ### Removed 19 | - `Validator.build()` deprecated in [v0.4.0+2] 20 | 21 | ## [v0.4.0+2] 22 | 23 | ### Added 24 | - `EachRule` 25 | - Made `Validator` a callable class 26 | - Allow adding rules via `Validator` constructor 27 | 28 | ### Deprecated 29 | - `Validator.build()` 30 | 31 | ### Changed 32 | - Moved all files to src folder. Use `import 'package:flrx_validator/flrx_validator.dart';` *BREAKING CHANGE* 33 | - Minor Code cleanup 34 | 35 | ### Fixed 36 | - Fixed `transformMessage` not applying properly in `AnyRule` 37 | 38 | ### Removed 39 | - OneOfRule (deprecated in v0.3.0) 40 | 41 | ## [v0.3.0] 42 | 43 | ### Added 44 | - `InRule`, `NotInRule` 45 | 46 | ### Deprecated 47 | - `OneOfRule` in favour of `InRule` 48 | 49 | ### Changed 50 | - Minor Code Cleanup. 51 | 52 | ## [v0.2.0] 53 | 54 | ### Added 55 | - Support for all Dart projects. 56 | 57 | ## [v0.1.0] 58 | 59 | Initial Release 60 | 61 | [v0.6.0]: https://github.com/flrx/validator/compare/v0.6.0...v0.5.0 62 | [v0.5.0]: https://github.com/flrx/validator/compare/v0.5.0...v0.4.0+2 63 | [v0.4.0+2]: https://github.com/flrx/validator/compare/v0.4.0+2...v0.3.0 64 | [v0.3.0]: https://github.com/flrx/validator/compare/v0.3.0...v0.2.0 65 | [v0.2.0]: https://github.com/flrx/validator/compare/v0.2.0...v0.1.0 66 | [v0.1.0]: https://github.com/flrx/validator/tag/v0.1.0 67 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [flrx.dev@gmail.com]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How do I make a contribution? 2 | 3 | Never made an open source contribution before? Wondering how contributions work in the in our project? Here's a quick rundown! 4 | 5 | 1. Find an issue that you are interested in addressing or a feature that you would like to add. Look for issues marked with "Good First Issue" if this is one of your first contributions to open source. 6 | 2. Fork the repository associated with the issue to your local GitHub organization. This means that you will have a copy of the repository under your-GitHub-username/repository-name. 7 | 3. Clone the repository to your local machine using `git clone https://github.com/github-username/repository-name.git`. 8 | 4. Create a new branch for your fix using `git checkout -b branch-name-here`. 9 | 5. Make the appropriate changes for the issue you are trying to address or the feature that you want to add. 10 | 6. Use `git add insert-paths-of-changed-files-here` to add the file contents of the changed files to the "snapshot" git uses to manage the state of the project, also known as the index. 11 | 7. Use `git commit -m "Insert a short message of the changes made here"` to store the contents of the index with a descriptive message. 12 | 8. Push the changes to the remote repository using `git push origin branch-name-here`. 13 | 9. Submit a pull request to the upstream repository. 14 | 10. Title the pull request with a short description of the changes made and the issue or bug number associated with your change. For example, you can title an issue like so "Added more log outputting to resolve #4352". 15 | 11. In the description of the pull request, explain the changes that you made, any issues you think exist with the pull request you made, and any questions you have for the maintainer. It's OK if your pull request is not perfect, the reviewer will be able to help you fix any problems and improve it! 16 | 12. Wait for the pull request to be reviewed by a maintainer. 17 | 13. Make changes to the pull request if the reviewing maintainer recommends them. 18 | 14. Celebrate your success after your pull request is merged! 19 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | 8 | ## Current Behavior 9 | 10 | 11 | 12 | 13 | ## Possible Solution 14 | 15 | 16 | 17 | 18 | ## Steps to Reproduce (for bugs) 19 | 20 | 21 | 22 | 23 | 1. 24 | 2. 25 | 3. 26 | 4. 27 | 28 | ## Context 29 | 30 | 31 | 32 | 33 | ## Your Environment 34 | 35 | 36 | 37 | - Version used: 38 | - Flutter/Dart version: 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 flrx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Related Issue 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## Motivation and Context 15 | 16 | 17 | 18 | ## How Has This Been Tested? 19 | 20 | 21 | 22 | 23 | 24 | ## Screenshots (if appropriate): 25 | 26 | ## Types of changes 27 | 28 | 29 | 30 | - [ ] Bug fix (non-breaking change which fixes an issue) 31 | - [ ] New feature (non-breaking change which adds functionality) 32 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 33 | 34 | ## Checklist: 35 | 36 | 37 | 38 | 39 | - [ ] My code follows the code style of this project. 40 | - [ ] My change requires a change to the documentation. 41 | - [ ] I have updated the documentation accordingly. 42 | - [ ] I have read the **CONTRIBUTING** document. 43 | - [ ] I have added tests to cover my changes. 44 | - [ ] All new and existing tests passed. 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flrx Validator 2 | 3 | ![Pub](https://img.shields.io/pub/v/flrx_validator) 4 | [![Build Status](https://api.cirrus-ci.com/github/flrx/validator.svg)](https://cirrus-ci.com/github/flrx/validator) 5 | [![codecov](https://codecov.io/gh/flrx/validator/branch/master/graph/badge.svg)](https://codecov.io/gh/flrx/validator) 6 | [![GitHub](https://img.shields.io/github/license/flrx/validator)](https://github.com/flrx/validator/blob/master/LICENSE) 7 | 8 | A fluent API based validator designed around flutter that promotes code reuse. It is highly extensible, modular and testable. 9 | 10 | ![Validator](doc/flrx_validator.png "Validator") 11 | 12 | ## Installation 13 | 14 | Add the following to your `pubspec.yaml` file 15 | 16 | ```yaml 17 | dependencies: 18 | flrx_validator: ^0.6.0 19 | ``` 20 | 21 | ## Usage 22 | 23 | ### Documentation 24 | 25 | For more info on Flrx Validator and Rules you can see the [**documentation**](https://flrx.github.io/validator). 26 | 27 | ### Built In Rules 28 | 29 | | Rule | Arguments | Description | 30 | |-----------------|--------------|-------------| 31 | | `AnyRule` | ruleList | Takes A list of rules, Passes when any of the rule passes, Useful for Validation of Username/Email Fields or similar fields| 32 | | `EachRule` | ruleList | Takes A list of rules, Passes when all of the rule passes and returns validation message for all rules, Useful for Validation of Password Fields or similar fields| 33 | | `EmailRule` | - | Passes when String is an E-Mail | 34 | | `InRule` | acceptedList | Passes when the value is included in the given list of values. | 35 | | `MaxLengthRule` | maxLength | Passes when the String length is less than the given length | 36 | | `MinLengthRule` | minLength | Passes when the String length is more than the given length | 37 | | `NotInRule` | rejectedList | Passes when the value is not included in the given list of values. | 38 | | `RegexRule` | regex | Passes when the value matches the given Regex. | 39 | | `RequiredRule` | - | Passes when the value is not null or empty. | 40 | 41 | Can't see a rule you need? Raise an issue or create a Pull Request. 42 | 43 | ### Example 44 | 45 | ```dart 46 | import 'package:flrx_validator/flrx_validator'; 47 | import 'package:flrx_validator/rules/email_rule.dart'; 48 | import 'package:flrx_validator/rules/required_rule.dart'; 49 | 50 | .... 51 | .... 52 | 53 | TextFormField( 54 | validator: Validator( 55 | rules: [RequiredRule(), EmailRule()] 56 | ) 57 | ); 58 | 59 | .... 60 | .... 61 | 62 | ``` 63 | 64 | ## Contributing 65 | 66 | Please see [**CONTRIBUTING**](https://github.com/flrx/validator/blob/develop/CONTRIBUTING.md). 67 | 68 | ## License 69 | 70 | Please see [**LICENSE**](https://github.com/flrx/validator/blob/develop/LICENSE). 71 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | linter: 4 | rules: 5 | - always_declare_return_types 6 | - always_put_control_body_on_new_line 7 | - always_put_required_named_parameters_first 8 | - annotate_overrides 9 | - avoid_bool_literals_in_conditional_expressions 10 | - close_sinks 11 | - package_prefixed_library_names 12 | - prefer_const_declarations 13 | - prefer_const_literals_to_create_immutables 14 | - prefer_interpolation_to_compose_strings 15 | - prefer_typing_uninitialized_variables 16 | - prefer_void_to_null 17 | - sort_pub_dependencies 18 | - unnecessary_await_in_return 19 | - unnecessary_brace_in_string_interps 20 | - unnecessary_getters_setters 21 | - unnecessary_lambdas 22 | - unnecessary_null_aware_assignments 23 | - unnecessary_overrides 24 | - unnecessary_statements -------------------------------------------------------------------------------- /doc/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "Flrx Validator", 3 | description: "Validator on Steroids", 4 | base: "/validator/", 5 | themeConfig: { 6 | displayAllHeaders: true, // Default: false, 7 | sidebar: ["/", "/validator", "/rule"], 8 | algolia: { 9 | apiKey: '1cc6df72a7d648335129bdef99facc02', 10 | indexName: 'flrx_validator' 11 | }, 12 | nav: [ 13 | { 14 | text: "Changelog", 15 | link: "https://github.com/flrx/validator/blob/master/CHANGELOG.md" 16 | }, 17 | { 18 | text: "API Docs", 19 | link: "https://pub.dev/documentation/flrx_validator/latest/" 20 | }, 21 | ], 22 | /* Repository Config */ 23 | repo: "flrx/validator", 24 | repoLabel: "Github", 25 | docsDir: "doc", 26 | docsBranch: "master", 27 | editLinks: true, 28 | editLinkText: "Help us improve this page!" 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | ## What is Flrx validator? 4 | 5 | It is a fluent API based FormField validator for flutter, that promotes code reuse. It is highly extensible, modular and testable. 6 | 7 | It is as simple as doing the following: 8 | 9 | ```dart 10 | TextFormField( 11 | validator: Validator( 12 | rules: [RequiredRule(), EmailRule()], 13 | ) 14 | ); 15 | ``` 16 | 17 | ## Why use this? 18 | 19 | There are many existing libraries solving this problem, but we did not find any solution which was made specifically for Flutter, was Dart 2 compatible, and at the same time was extensible. 20 | 21 | We wanted a solution which was so flexible that if we wanted to localize or transform the messages given by the validator, we could do so and still be able to keep it clean. 22 | 23 | ## Getting started 24 | 25 | Add the following to your `pubspec.yaml` file: 26 | 27 | ```yaml 28 | dependencies: 29 | flrx_validator: ^0.4.0+2 30 | ``` 31 | -------------------------------------------------------------------------------- /doc/code/custom_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | 3 | class CustomRule extends Rule { 4 | CustomRule({required this.customParam, validationMessage}) : super(validationMessage); 5 | 6 | final String customParam; 7 | 8 | @override 9 | String? onValidate(String entityName, T? value) { 10 | return null; 11 | } 12 | 13 | @override 14 | Map getRuleSpecificParams() { 15 | return {'customParam': customParam}; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /doc/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # abort on errors 4 | set -e 5 | 6 | cd .vuepress/dist 7 | 8 | # if you are deploying to a custom domain 9 | # echo 'www.example.com' > CNAME 10 | 11 | git config --global user.email "flrx.dev@gmail.com" 12 | git config --global user.name "Cirrus CI: Flrx Validator" 13 | git init 14 | git add -A 15 | git commit -m 'deploy' 16 | git remote add origin https://$DEPLOYMENT_TOKEN@github.com/flrx/validator.git 17 | git push -f -u origin master:gh-pages 18 | 19 | cd - -------------------------------------------------------------------------------- /doc/each_rule.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/doc/each_rule.jpg -------------------------------------------------------------------------------- /doc/flrx_validator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/doc/flrx_validator.png -------------------------------------------------------------------------------- /doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "docs:build": "vuepress build" 4 | }, 5 | "dependencies": { 6 | "vuepress": "^1.0.3" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /doc/rule.md: -------------------------------------------------------------------------------- 1 | # Rule 2 | 3 | [[toc]] 4 | 5 | ## Introduction 6 | 7 | `Rule` encapsulates validation logic and its associated validation message. They are reusable and easily testable. 8 | 9 | ## Built-in Rules 10 | 11 | Flrx Validator comes with lot of built-in rules: 12 | 13 | ### RequiredRule 14 | 15 | This `Rule` validates if the input provided to it is not empty. 16 | * It explicitly supports String, Map and Iterable. 17 | * It implicitly supports any type which has the length getter. 18 | * For any other type, the nullability of the value is tested, 19 | 20 | ```dart 21 | Validator(rules: [RequiredRule()]) 22 | ``` 23 | 24 | **Output** 25 | 26 | ``` 27 | Entity is required 28 | ``` 29 | 30 | ### MaxLengthRule 31 | 32 | This `Rule` validates if the input's length is less than the max limit. 33 | * It explicitly supports String, Map and Iterable. 34 | * It implicitly supports any type which has the length getter. 35 | 36 | ```dart 37 | Validator(rules: [MaxLengthRule(20)]) 38 | ``` 39 | 40 | **Output** 41 | 42 | ``` 43 | Entity should be less than 20 characters 44 | ``` 45 | 46 | To use it with your custom types, initialize the rule with dynamic Type. 47 | 48 | ```dart 49 | Validator(rules: [MaxLengthRule(20)]) 50 | ``` 51 | 52 | ### MinLengthRule 53 | 54 | This `Rule` validates if the input's length is more than the min limit. 55 | 56 | ```dart 57 | Validator(rules: [MinLengthRule(6)]) 58 | ``` 59 | 60 | **Output** 61 | 62 | ``` 63 | Entity should be more than 6 characters 64 | ``` 65 | The supported types and usage are similar to `MaxLengthRule` 66 | 67 | ### NumberRule 68 | 69 | This `Rule` validates if the input is a number. 70 | 71 | ```dart 72 | Validator(rules: [NumberRule()]) 73 | ``` 74 | 75 | **Output** 76 | 77 | ``` 78 | Value is not a number 79 | ``` 80 | 81 | ### MinRule 82 | 83 | This `Rule` validates if the input is a number and is greater than a specific number. 84 | 85 | ```dart 86 | Validator(rules: [MinRule(20)]) 87 | ``` 88 | 89 | **Output** 90 | 91 | ``` 92 | Value should be greater than 20 93 | ``` 94 | 95 | ### MaxRule 96 | 97 | This `Rule` validates if the input is a number. 98 | 99 | ```dart 100 | Validator(rules: [MaxRule(100)]) 101 | ``` 102 | 103 | **Output** 104 | 105 | ``` 106 | Value should be lesser than 100 107 | ``` 108 | 109 | ### RegexRule 110 | 111 | This `Rule` validates if the input matches a `RegEx` pattern. 112 | 113 | ```dart 114 | Validator(rules: [RegexRule(r"([(+]*[0-9]+[()+. -]*)")]) 115 | ``` 116 | 117 | **Output** 118 | 119 | ``` 120 | Value is not a valid pattern 121 | ``` 122 | 123 | ### EmailRule 124 | 125 | This `Rule` is an extension of `RegexRule` which checks the input against the email regex pattern. 126 | 127 | ```dart 128 | Validator(rules: [EmailRule()]) 129 | ``` 130 | 131 | **Output** 132 | 133 | ``` 134 | Value is not a valid email address 135 | ``` 136 | 137 | ### InRule 138 | 139 | This `Rule` checks if the value provided is in the list of accepted values. 140 | 141 | ```dart 142 | Validator(rules: [InRule(['value1', 'value2', 'value3'])]) 143 | ``` 144 | 145 | **Output** 146 | 147 | ``` 148 | Entity is not in the list of accepted values. 149 | ``` 150 | 151 | ### NotInRule 152 | 153 | This `Rule` checks if the value provided is not in the list of rejected values. 154 | 155 | ```dart 156 | Validator(rules: [NotInRule(['value1', 'value2', 'value3'])]) 157 | ``` 158 | 159 | **Output** 160 | 161 | ``` 162 | Entity is in list of rejected values. 163 | ``` 164 | 165 | ### AnyRule 166 | 167 | This `Rule` checks whether the input provided passes any of the `Rule`s provided to `AnyRule`. 168 | 169 | ```dart 170 | Validator(rules: [AnyRule([MinLengthRule(6), EmailRule()])]) 171 | ``` 172 | 173 | **Output** 174 | 175 | ``` 176 | Value is not a valid email address. 177 | ``` 178 | 179 | ### EachRule 180 | 181 | This `Rule` checks whether the input provided passes all of the `Rule`s provided to `EachRule`. 182 | This rule also returns the validation message for all `Rule`s passed to it. 183 | 184 | ```dart 185 | Validator(rules: [ 186 | EachRule([ 187 | MinLengthRule(8), 188 | RegexRule( 189 | r'(?=.*[a-z])', 190 | validationMessage: ":entity should contain one lowercase character", 191 | ), 192 | ]) 193 | ]); 194 | ``` 195 | 196 | **Output** 197 | 198 | ![Validator](each_rule.jpg "Each Rule Output") 199 | 200 | `EachRule` also accepts a `concatenator` which concatenates all the validation messages. 201 | 202 | ## Custom Validation Rule 203 | 204 | The built-in rules are not an exhaustive list of rules that can be possible. Hence the user can always extend the `Rule` class and create custom validation `Rule`s according to the requirements. 205 | 206 | The custom validation rule can be written this way: 207 | 208 | <<< @/code/custom_rule.dart 209 | 210 | ## Custom Validation Message 211 | 212 | All rules can take a custom validation message in case you want to show something else instead of the built in message. 213 | 214 | You can pass a custom message as follows 215 | 216 | ```dart 217 | Validator(rules: [ 218 | RequiredRule(validationMessage:"Email is needed for creating an account") 219 | ]) 220 | ``` 221 | 222 | **Output** 223 | 224 | ``` 225 | Email is needed for creating an account 226 | ``` 227 | 228 | ## Transforming the message 229 | 230 | All rules can take `transformMessage` function as well just like the `Validator`. 231 | 232 | You can refer [**`Validator.transformMessage()`**](./validator.html#transforming-the-message) for more Info. 233 | -------------------------------------------------------------------------------- /doc/validator.md: -------------------------------------------------------------------------------- 1 | # Validator 2 | 3 | [[toc]] 4 | 5 | ## Introduction 6 | 7 | Flrx Validator allows you to take a specific value and validate against a set of rules. 8 | It is primarily designed to be used by Flutter's `FormField`'s validator, however you can use it with any Dart 2 Project. 9 | 10 | The `Validator` takes a List of `Rule`. The value supplied to the 11 | `Validator` is supplied to the `Rule`s in the order they were registered. 12 | The error message of the first `Rule` that fails is returned back. If all the `Rule`s pass, then null is returned to the `FormField`. 13 | 14 | ## Running the Validator 15 | 16 | There are two ways to run the validator. 17 | 18 | ### Through the instance 19 | 20 | The `Validator` instance itself can be passed to a `FormField` Widget if you want to add validation. 21 | 22 | ```dart 23 | TextFormField(validator: Validator(rules: [])) 24 | ``` 25 | 26 | 27 | ### Via `validate()` 28 | 29 | The `Validator`'s `validate()` method is useful if you want to validate a value outside a `FormField`. 30 | For example, Flutter does not have any Cupertino FormField Widgets. For such cases you can perform validation like this: 31 | 32 | ```dart 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | 37 | ... 38 | CupertinoTextField(controller: _textEditingController), 39 | Text(emailValidationMessage), 40 | ... 41 | 42 | } 43 | 44 | void onFormSubmitPressed() { 45 | String value = _textEditingController.text; 46 | setState(() { 47 | emailValidationMessage = Validator(rules: []).validate(value) ?? ''; 48 | }); 49 | } 50 | 51 | ... 52 | 53 | } 54 | ``` 55 | 56 | You can see the full example [here](https://github.com/flrx/validator/blob/master/example/lib/cupertino_form.dart). 57 | 58 | ::: warning 59 | Although for simple forms this may seem like a good idea, this could quickly go out of hand. 60 | You might want to use a Custom [FormField](https://api.flutter.dev/flutter/widgets/FormField-class.html) Widget instead. 61 | ::: 62 | 63 | ## Adding Rules 64 | 65 | The `Validator` takes `Rule`s against which the value is validated. You can add `Rule`s to the validator in the following ways 66 | 67 | ### Via `Validator()` constructor 68 | 69 | ```dart 70 | TextFormField(validator: Validator(rules: [RequiredRule()])) 71 | ``` 72 | 73 | As `Validator`'s add method returns `Validator` instance, therefore we can chain it to do something like this. 74 | 75 | ### Via `add()` 76 | 77 | ```dart 78 | TextFormField( 79 | validator: Validator() 80 | .add(RequiredRule()) 81 | ) 82 | ``` 83 | 84 | As `Validator`'s add method returns `Validator` instance, therefore we can chain it to do something like this. 85 | 86 | ```dart 87 | TextFormField( 88 | validator: Validator() 89 | .add(RequiredRule()) 90 | .add(EmailRule()) 91 | ) 92 | ``` 93 | 94 | ### Via `addAll()` 95 | 96 | If you have multiple `Rule`s to add and you do not want to chain the `add` method, then `addAll` method can be used as shown below: 97 | 98 | ```dart 99 | TextFormField( 100 | validator: Validator() 101 | .addAll([RequiredRule(), EmailRule()]) 102 | ) 103 | ``` 104 | 105 | ::: tip Remember 106 | The **`Validator`** runs the **`Rule`**'s in the order they are registered. 107 | ::: 108 | 109 | ## Specifying name of the entity 110 | 111 | `Validator` also accepts the name of the entity/field on which it is run, this way it is able to customize its validation message and put the `entityName` provided in its constructor. 112 | 113 | ```dart 114 | TextFormField( 115 | validator: Validator( 116 | rules:[MinLengthRule(6)], 117 | entityName: 'Password', 118 | ) 119 | ) 120 | ``` 121 | 122 | will produce a validation message like: 123 | 124 | ``` 125 | Password should be more than 6 characters 126 | ``` 127 | 128 | ## Transforming the message 129 | 130 | `Validator` also accepts a function which can be used to transform the message that is returned after the validation of the input. 131 | 132 | `transformMessage` function can be used for cases like localization among others. 133 | 134 | By default, `transformMessage` replaces the `keys` in the `message` by the corresponding `values` which are defined in the `params` map. 135 | 136 | Consider a situation where we want to change the case of the message to upperCase, it can be done as follows: 137 | 138 | ```dart 139 | TextFormField( 140 | rules: [RequiredRule()], 141 | validator: Validator( 142 | transformMessage: (String message, Map params) => 143 | message.toUpperCase(); 144 | ) 145 | ) 146 | ``` 147 | 148 | ::: tip Remember 149 | The **`Rule`** also accepts the **`transformMessage`** which takes precedence over the **`Validator`**'s **`transformMessage`**. 150 | ::: 151 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_export_environment.sh 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /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: 20e59316b8b8474554b38493b8ca888794b0234a 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Validator Examples 2 | 3 | ## Using FormField 4 | 5 | ### example/lib/material_form.dart 6 | 7 | ```dart 8 | import 'package:flrx_validator/rules/email_rule.dart'; 9 | import 'package:flrx_validator/rules/required_rule.dart'; 10 | import 'package:flrx_validator/flrx_validator'; 11 | import 'package:flutter/material.dart'; 12 | 13 | class MaterialForm extends StatefulWidget { 14 | MaterialForm({Key key}) : super(key: key); 15 | 16 | @override 17 | _MaterialFormState createState() => _MaterialFormState(); 18 | } 19 | 20 | class _MaterialFormState extends State { 21 | final _formKey = GlobalKey(); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold( 26 | appBar: AppBar(title: Text('Material Form Validator')), 27 | body: SingleChildScrollView( 28 | padding: const EdgeInsets.all(16), 29 | child: Form( 30 | key: _formKey, 31 | child: Column( 32 | crossAxisAlignment: CrossAxisAlignment.start, 33 | children: [ 34 | TextFormField( 35 | validator: 36 | Validator().add(RequiredRule()).add(EmailRule()), 37 | decoration: InputDecoration(hintText: 'Email'), 38 | ), 39 | buildDropdown(), 40 | Padding( 41 | padding: const EdgeInsets.symmetric(vertical: 16.0), 42 | child: RaisedButton( 43 | onPressed: onFormSubmitPressed, 44 | child: Text('Submit'), 45 | ), 46 | ), 47 | ], 48 | ), 49 | ), 50 | ), 51 | ); 52 | } 53 | 54 | Widget buildDropdown() { 55 | return DropdownButtonFormField( 56 | validator: Validator().add(RequiredRule()), 57 | value: "", 58 | items: >[ 59 | DropdownMenuItem( 60 | child: Text('Please select an Item'), 61 | value: "", 62 | ), 63 | DropdownMenuItem( 64 | child: Text('Item 1'), 65 | value: "Item 1", 66 | ) 67 | ], 68 | ); 69 | } 70 | 71 | void onFormSubmitPressed() { 72 | if (_formKey.currentState.validate()) { 73 | // Process data. 74 | } 75 | } 76 | } 77 | ``` 78 | 79 | ## Using TextEditingController 80 | 81 | ### example/lib/cupertino_form.dart 82 | 83 | ```dart 84 | import 'package:flrx_validator/rules/email_rule.dart'; 85 | import 'package:flrx_validator/rules/required_rule.dart'; 86 | import 'package:flrx_validator/flrx_validator'; 87 | import 'package:flutter/cupertino.dart'; 88 | 89 | class CupertinoForm extends StatefulWidget { 90 | @override 91 | _CupertinoFormState createState() => _CupertinoFormState(); 92 | } 93 | 94 | class _CupertinoFormState extends State { 95 | TextEditingController _textEditingController = TextEditingController(); 96 | String emailValidationMessage = ""; 97 | 98 | @override 99 | Widget build(BuildContext context) { 100 | return DefaultTextStyle( 101 | style: const TextStyle( 102 | fontFamily: '.SF UI Text', 103 | inherit: false, 104 | fontSize: 17.0, 105 | color: CupertinoColors.black, 106 | ), 107 | child: CupertinoPageScaffold( 108 | navigationBar: CupertinoNavigationBar( 109 | previousPageTitle: "Form Selection", 110 | middle: Text('Cupertino Form Validation'), 111 | ), 112 | child: CupertinoScrollbar( 113 | child: SingleChildScrollView( 114 | padding: 115 | const EdgeInsets.symmetric(vertical: 32.0, horizontal: 16.0), 116 | child: Column( 117 | crossAxisAlignment: CrossAxisAlignment.start, 118 | children: [ 119 | SizedBox(height: 72), 120 | CupertinoTextField( 121 | controller: _textEditingController, 122 | prefixMode: OverlayVisibilityMode.always, 123 | placeholder: 'Email', 124 | prefix: Icon( 125 | CupertinoIcons.mail_solid, 126 | color: CupertinoColors.lightBackgroundGray, 127 | size: 28.0, 128 | ), 129 | decoration: BoxDecoration( 130 | border: Border( 131 | bottom: BorderSide( 132 | width: 0.0, color: CupertinoColors.inactiveGray)), 133 | ), 134 | padding: 135 | EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0), 136 | clearButtonMode: OverlayVisibilityMode.editing, 137 | ), 138 | Padding( 139 | padding: const EdgeInsets.fromLTRB(34, 8, 16, 16), 140 | child: Text( 141 | emailValidationMessage, 142 | style: TextStyle( 143 | color: CupertinoColors.destructiveRed, 144 | fontSize: 12, 145 | ), 146 | ), 147 | ), 148 | Center( 149 | child: CupertinoButton.filled( 150 | child: Text('Submit'), onPressed: onFormSubmitPressed), 151 | ) 152 | ], 153 | ), 154 | ), 155 | )), 156 | ); 157 | } 158 | 159 | @override 160 | void dispose() { 161 | _textEditingController.dispose(); 162 | super.dispose(); 163 | } 164 | 165 | void onFormSubmitPressed() { 166 | String value = _textEditingController.text; 167 | setState(() { 168 | emailValidationMessage = Validator() 169 | .add(RequiredRule()) 170 | .add(EmailRule()) 171 | .validate(value) ?? 172 | ''; 173 | }); 174 | } 175 | } 176 | ``` 177 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.72' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.enableR8=true 4 | android.useAndroidX=true 5 | android.enableJetifier=true 6 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /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/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /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/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/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/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | 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 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/cupertino_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | class CupertinoForm extends StatefulWidget { 5 | const CupertinoForm({Key? key}) : super(key: key); 6 | 7 | @override 8 | _CupertinoFormState createState() => _CupertinoFormState(); 9 | } 10 | 11 | class _CupertinoFormState extends State { 12 | final TextEditingController _textEditingController = TextEditingController(); 13 | String emailValidationMessage = ""; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return DefaultTextStyle( 18 | style: const TextStyle( 19 | fontFamily: '.SF UI Text', 20 | inherit: false, 21 | fontSize: 17.0, 22 | color: CupertinoColors.black, 23 | ), 24 | child: CupertinoPageScaffold( 25 | navigationBar: const CupertinoNavigationBar( 26 | previousPageTitle: "Form Selection", 27 | middle: Text('Cupertino Form Validation'), 28 | ), 29 | child: CupertinoScrollbar( 30 | child: SingleChildScrollView( 31 | padding: 32 | const EdgeInsets.symmetric(vertical: 32.0, horizontal: 16.0), 33 | child: Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | const SizedBox(height: 72), 37 | CupertinoTextField( 38 | controller: _textEditingController, 39 | prefixMode: OverlayVisibilityMode.always, 40 | placeholder: 'Email', 41 | prefix: const Icon( 42 | CupertinoIcons.mail_solid, 43 | color: CupertinoColors.lightBackgroundGray, 44 | size: 28.0, 45 | ), 46 | decoration: const BoxDecoration( 47 | border: Border( 48 | bottom: BorderSide( 49 | width: 0.0, color: CupertinoColors.inactiveGray)), 50 | ), 51 | padding: 52 | const EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0), 53 | clearButtonMode: OverlayVisibilityMode.editing, 54 | ), 55 | Padding( 56 | padding: const EdgeInsets.fromLTRB(34, 8, 16, 16), 57 | child: Text( 58 | emailValidationMessage, 59 | style: const TextStyle( 60 | color: CupertinoColors.destructiveRed, 61 | fontSize: 12, 62 | ), 63 | ), 64 | ), 65 | Center( 66 | child: CupertinoButton.filled( 67 | child: const Text('Submit'), onPressed: onFormSubmitPressed), 68 | ) 69 | ], 70 | ), 71 | ), 72 | )), 73 | ); 74 | } 75 | 76 | @override 77 | void dispose() { 78 | _textEditingController.dispose(); 79 | super.dispose(); 80 | } 81 | 82 | void onFormSubmitPressed() { 83 | String value = _textEditingController.text; 84 | setState(() { 85 | emailValidationMessage = Validator(rules: []) 86 | .add(RequiredRule()) 87 | .add(EmailRule()) 88 | .validate(value) ?? 89 | ''; 90 | }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'material_form.dart'; 3 | import 'cupertino_form.dart'; 4 | 5 | void main() => runApp(const MyApp()); 6 | 7 | const String _title = 'Form Validation'; 8 | 9 | /// This Widget is the main application widget. 10 | class MyApp extends StatelessWidget { 11 | const MyApp({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return MaterialApp( 16 | title: _title, 17 | home: const SelectTypeOfForm(), 18 | routes: { 19 | '/material-form': (BuildContext context) => const MaterialForm(), 20 | '/cupertino-form': (BuildContext context) => const CupertinoForm() 21 | }, 22 | ); 23 | } 24 | } 25 | 26 | class SelectTypeOfForm extends StatelessWidget { 27 | const SelectTypeOfForm({Key? key}) : super(key: key); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | appBar: AppBar(title: const Text(_title)), 33 | body: Center( 34 | child: Column( 35 | mainAxisSize: MainAxisSize.min, 36 | children: [ 37 | ElevatedButton( 38 | child: const Text("Material Form"), 39 | onPressed: () { 40 | Navigator.of(context).pushNamed('/material-form'); 41 | }, 42 | ), 43 | ElevatedButton( 44 | child: const Text("Cupertino Form"), 45 | onPressed: () { 46 | Navigator.of(context).pushNamed('/cupertino-form'); 47 | }, 48 | ) 49 | ], 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/lib/material_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:multiselect_formfield/multiselect_formfield.dart'; 4 | 5 | class MaterialForm extends StatefulWidget { 6 | const MaterialForm({Key? key}) : super(key: key); 7 | 8 | @override 9 | _MaterialFormState createState() => _MaterialFormState(); 10 | } 11 | 12 | class _MaterialFormState extends State { 13 | final GlobalKey _formKey = GlobalKey(); 14 | List _myActivities = []; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | appBar: AppBar(title: const Text('Material Form Validator')), 20 | body: SingleChildScrollView( 21 | padding: const EdgeInsets.all(16), 22 | child: Form( 23 | key: _formKey, 24 | child: Column( 25 | crossAxisAlignment: CrossAxisAlignment.start, 26 | children: [ 27 | TextFormField( 28 | validator: Validator(rules: [ 29 | RequiredRule(), 30 | EmailRule(), 31 | ]), 32 | decoration: const InputDecoration(hintText: 'Email'), 33 | ), 34 | TextFormField( 35 | validator: Validator(rules: [ 36 | RequiredRule(), 37 | EachRule( 38 | >[ 39 | MinLengthRule(8), 40 | RegexRule( 41 | r'(?=.*[a-z])', 42 | validationMessage: 43 | ":entity should contain one lowercase character", 44 | ), 45 | ], 46 | ) 47 | ]), 48 | decoration: const InputDecoration(hintText: 'Password'), 49 | obscureText: true, 50 | ), 51 | buildStringDropdown(), 52 | buildIntDropdown(), 53 | buildMultiSelectField(), 54 | Padding( 55 | padding: const EdgeInsets.symmetric(vertical: 16.0), 56 | child: ElevatedButton( 57 | onPressed: onFormSubmitPressed, 58 | child: const Text('Submit'), 59 | ), 60 | ), 61 | ], 62 | ), 63 | ), 64 | ), 65 | ); 66 | } 67 | 68 | Widget buildStringDropdown() { 69 | return DropdownButtonFormField( 70 | validator: Validator(rules: [RequiredRule()]), 71 | value: "", 72 | items: const >[ 73 | DropdownMenuItem( 74 | child: Text('Please select an Item'), 75 | value: "", 76 | ), 77 | DropdownMenuItem( 78 | child: Text('Item 1'), 79 | value: "Item 1", 80 | ) 81 | ], 82 | onChanged: (String? value) {}, 83 | ); 84 | } 85 | 86 | Widget buildIntDropdown() { 87 | return DropdownButtonFormField( 88 | validator: Validator(rules: [RequiredRule()]), 89 | value: 1, 90 | items: const >[ 91 | DropdownMenuItem( 92 | child: Text('Please select an Item'), 93 | value: null, 94 | ), 95 | DropdownMenuItem( 96 | child: Text('Item 1'), 97 | value: 1, 98 | ), 99 | DropdownMenuItem( 100 | child: Text('Item 2'), 101 | value: 2, 102 | ) 103 | ], 104 | onChanged: (int? value) {}, 105 | ); 106 | } 107 | 108 | void onFormSubmitPressed() { 109 | if (_formKey.currentState?.validate() == true) { 110 | // Process data. 111 | } 112 | } 113 | 114 | Widget buildMultiSelectField() { 115 | return MultiSelectFormField( 116 | autovalidate: false, 117 | title: const Text('My workouts'), 118 | validator: Validator(rules: >>[ 119 | RequiredRule>(), 120 | ]), 121 | dataSource: const >[ 122 | { 123 | "display": "Running", 124 | "value": "Running", 125 | }, 126 | { 127 | "display": "Climbing", 128 | "value": "Climbing", 129 | }, 130 | { 131 | "display": "Walking", 132 | "value": "Walking", 133 | }, 134 | { 135 | "display": "Swimming", 136 | "value": "Swimming", 137 | }, 138 | { 139 | "display": "Soccer Practice", 140 | "value": "Soccer Practice", 141 | }, 142 | { 143 | "display": "Baseball Practice", 144 | "value": "Baseball Practice", 145 | }, 146 | { 147 | "display": "Football Practice", 148 | "value": "Football Practice", 149 | }, 150 | ], 151 | textField: 'display', 152 | valueField: 'value', 153 | okButtonLabel: 'OK', 154 | cancelButtonLabel: 'CANCEL', 155 | // required: true, 156 | hintWidget: const Text('Please choose one or more'), 157 | initialValue: _myActivities, 158 | onSaved: (value) { 159 | setState(() { 160 | _myActivities = value; 161 | }); 162 | }, 163 | ); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /example/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(runner LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "example") 5 | set(APPLICATION_ID "com.example.example") 6 | 7 | cmake_policy(SET CMP0063 NEW) 8 | 9 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 10 | 11 | # Root filesystem for cross-building. 12 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 13 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 14 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 15 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 16 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 18 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 19 | endif() 20 | 21 | # Configure build options. 22 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 23 | set(CMAKE_BUILD_TYPE "Debug" CACHE 24 | STRING "Flutter build mode" FORCE) 25 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 26 | "Debug" "Profile" "Release") 27 | endif() 28 | 29 | # Compilation settings that should be applied to most targets. 30 | function(APPLY_STANDARD_SETTINGS TARGET) 31 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 32 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 33 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 34 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 35 | endfunction() 36 | 37 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 38 | 39 | # Flutter library and tool build rules. 40 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 41 | 42 | # System-level dependencies. 43 | find_package(PkgConfig REQUIRED) 44 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 45 | 46 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 47 | 48 | # Application build 49 | add_executable(${BINARY_NAME} 50 | "main.cc" 51 | "my_application.cc" 52 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 53 | ) 54 | apply_standard_settings(${BINARY_NAME}) 55 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 56 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 57 | add_dependencies(${BINARY_NAME} flutter_assemble) 58 | # Only the install-generated bundle's copy of the executable will launch 59 | # correctly, since the resources must in the right relative locations. To avoid 60 | # people trying to run the unbundled copy, put it in a subdirectory instead of 61 | # the default top-level location. 62 | set_target_properties(${BINARY_NAME} 63 | PROPERTIES 64 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 65 | ) 66 | 67 | # Generated plugin build rules, which manage building the plugins and adding 68 | # them to the application. 69 | include(flutter/generated_plugins.cmake) 70 | 71 | 72 | # === Installation === 73 | # By default, "installing" just makes a relocatable bundle in the build 74 | # directory. 75 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 76 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 77 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 78 | endif() 79 | 80 | # Start with a clean build bundle directory every time. 81 | install(CODE " 82 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 83 | " COMPONENT Runtime) 84 | 85 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 86 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 87 | 88 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 89 | COMPONENT Runtime) 90 | 91 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 92 | COMPONENT Runtime) 93 | 94 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 95 | COMPONENT Runtime) 96 | 97 | if(PLUGIN_BUNDLED_LIBRARIES) 98 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 99 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 100 | COMPONENT Runtime) 101 | endif() 102 | 103 | # Fully re-copy the assets directory on each build to avoid having stale files 104 | # from a previous install. 105 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 106 | install(CODE " 107 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 108 | " COMPONENT Runtime) 109 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 110 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 111 | 112 | # Install the AOT library on non-Debug builds only. 113 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 114 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 115 | COMPONENT Runtime) 116 | endif() 117 | -------------------------------------------------------------------------------- /example/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | 11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 12 | # which isn't available in 3.10. 13 | function(list_prepend LIST_NAME PREFIX) 14 | set(NEW_LIST "") 15 | foreach(element ${${LIST_NAME}}) 16 | list(APPEND NEW_LIST "${PREFIX}${element}") 17 | endforeach(element) 18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | # === Flutter Library === 22 | # System-level dependencies. 23 | find_package(PkgConfig REQUIRED) 24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 27 | pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) 28 | pkg_check_modules(LZMA REQUIRED IMPORTED_TARGET liblzma) 29 | 30 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 31 | 32 | # Published to parent scope for install step. 33 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 34 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 35 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 36 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 37 | 38 | list(APPEND FLUTTER_LIBRARY_HEADERS 39 | "fl_basic_message_channel.h" 40 | "fl_binary_codec.h" 41 | "fl_binary_messenger.h" 42 | "fl_dart_project.h" 43 | "fl_engine.h" 44 | "fl_json_message_codec.h" 45 | "fl_json_method_codec.h" 46 | "fl_message_codec.h" 47 | "fl_method_call.h" 48 | "fl_method_channel.h" 49 | "fl_method_codec.h" 50 | "fl_method_response.h" 51 | "fl_plugin_registrar.h" 52 | "fl_plugin_registry.h" 53 | "fl_standard_message_codec.h" 54 | "fl_standard_method_codec.h" 55 | "fl_string_codec.h" 56 | "fl_value.h" 57 | "fl_view.h" 58 | "flutter_linux.h" 59 | ) 60 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 61 | add_library(flutter INTERFACE) 62 | target_include_directories(flutter INTERFACE 63 | "${EPHEMERAL_DIR}" 64 | ) 65 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 66 | target_link_libraries(flutter INTERFACE 67 | PkgConfig::GTK 68 | PkgConfig::GLIB 69 | PkgConfig::GIO 70 | PkgConfig::BLKID 71 | PkgConfig::LZMA 72 | ) 73 | add_dependencies(flutter flutter_assemble) 74 | 75 | # === Flutter tool backend === 76 | # _phony_ is a non-existent file to force this command to run every time, 77 | # since currently there's no way to get a full input/output list from the 78 | # flutter tool. 79 | add_custom_command( 80 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 81 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 82 | COMMAND ${CMAKE_COMMAND} -E env 83 | ${FLUTTER_TOOL_ENVIRONMENT} 84 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 85 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 86 | VERBATIM 87 | ) 88 | add_custom_target(flutter_assemble DEPENDS 89 | "${FLUTTER_LIBRARY}" 90 | ${FLUTTER_LIBRARY_HEADERS} 91 | ) 92 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void fl_register_plugins(FlPluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | set(PLUGIN_BUNDLED_LIBRARIES) 9 | 10 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 11 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 12 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 13 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 14 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 15 | endforeach(plugin) 16 | -------------------------------------------------------------------------------- /example/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /example/linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen *screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "example"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } 47 | else { 48 | gtk_window_set_title(window, "example"); 49 | } 50 | 51 | gtk_window_set_default_size(window, 1280, 720); 52 | gtk_widget_show(GTK_WIDGET(window)); 53 | 54 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 55 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 56 | 57 | FlView* view = fl_view_new(project); 58 | gtk_widget_show(GTK_WIDGET(view)); 59 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 60 | 61 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 62 | 63 | gtk_widget_grab_focus(GTK_WIDGET(view)); 64 | } 65 | 66 | // Implements GApplication::local_command_line. 67 | static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) { 68 | MyApplication* self = MY_APPLICATION(application); 69 | // Strip out the first argument as it is the binary name. 70 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 71 | 72 | g_autoptr(GError) error = nullptr; 73 | if (!g_application_register(application, nullptr, &error)) { 74 | g_warning("Failed to register: %s", error->message); 75 | *exit_status = 1; 76 | return TRUE; 77 | } 78 | 79 | g_application_activate(application); 80 | *exit_status = 0; 81 | 82 | return TRUE; 83 | } 84 | 85 | // Implements GObject::dispose. 86 | static void my_application_dispose(GObject *object) { 87 | MyApplication* self = MY_APPLICATION(object); 88 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 89 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 90 | } 91 | 92 | static void my_application_class_init(MyApplicationClass* klass) { 93 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 94 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 95 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 96 | } 97 | 98 | static void my_application_init(MyApplication* self) {} 99 | 100 | MyApplication* my_application_new() { 101 | return MY_APPLICATION(g_object_new(my_application_get_type(), 102 | "application-id", APPLICATION_ID, 103 | "flags", G_APPLICATION_NON_UNIQUE, 104 | nullptr)); 105 | } 106 | -------------------------------------------------------------------------------- /example/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | 9 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 10 | } 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | publish_to: none 16 | environment: 17 | sdk: ">=2.12.0 <3.0.0" 18 | 19 | dependencies: 20 | cupertino_icons: ^1.0.2 21 | flrx_validator: 22 | path: ../ 23 | flutter: 24 | sdk: flutter 25 | multiselect_formfield: ^0.1.6 26 | 27 | 28 | dev_dependencies: 29 | flutter_lints: ^1.0.4 30 | flutter_test: 31 | sdk: flutter 32 | 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://dart.dev/tools/pub/pubspec 36 | 37 | # The following section is specific to Flutter. 38 | flutter: 39 | 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.dev/custom-fonts/#from-packages 75 | -------------------------------------------------------------------------------- /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(const 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 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | example 27 | 28 | 29 | 30 | 33 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /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 | } 24 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(example LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "example") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 11 | 12 | # === Flutter Library === 13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 14 | 15 | # Published to parent scope for install step. 16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 20 | 21 | list(APPEND FLUTTER_LIBRARY_HEADERS 22 | "flutter_export.h" 23 | "flutter_windows.h" 24 | "flutter_messenger.h" 25 | "flutter_plugin_registrar.h" 26 | "flutter_texture_registrar.h" 27 | ) 28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 29 | add_library(flutter INTERFACE) 30 | target_include_directories(flutter INTERFACE 31 | "${EPHEMERAL_DIR}" 32 | ) 33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 34 | add_dependencies(flutter flutter_assemble) 35 | 36 | # === Wrapper === 37 | list(APPEND CPP_WRAPPER_SOURCES_CORE 38 | "core_implementations.cc" 39 | "standard_codec.cc" 40 | ) 41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 43 | "plugin_registrar.cc" 44 | ) 45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 46 | list(APPEND CPP_WRAPPER_SOURCES_APP 47 | "flutter_engine.cc" 48 | "flutter_view_controller.cc" 49 | ) 50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 51 | 52 | # Wrapper sources needed for a plugin. 53 | add_library(flutter_wrapper_plugin STATIC 54 | ${CPP_WRAPPER_SOURCES_CORE} 55 | ${CPP_WRAPPER_SOURCES_PLUGIN} 56 | ) 57 | apply_standard_settings(flutter_wrapper_plugin) 58 | set_target_properties(flutter_wrapper_plugin PROPERTIES 59 | POSITION_INDEPENDENT_CODE ON) 60 | set_target_properties(flutter_wrapper_plugin PROPERTIES 61 | CXX_VISIBILITY_PRESET hidden) 62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 63 | target_include_directories(flutter_wrapper_plugin PUBLIC 64 | "${WRAPPER_ROOT}/include" 65 | ) 66 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 67 | 68 | # Wrapper sources needed for the runner. 69 | add_library(flutter_wrapper_app STATIC 70 | ${CPP_WRAPPER_SOURCES_CORE} 71 | ${CPP_WRAPPER_SOURCES_APP} 72 | ) 73 | apply_standard_settings(flutter_wrapper_app) 74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 75 | target_include_directories(flutter_wrapper_app PUBLIC 76 | "${WRAPPER_ROOT}/include" 77 | ) 78 | add_dependencies(flutter_wrapper_app flutter_assemble) 79 | 80 | # === Flutter tool backend === 81 | # _phony_ is a non-existent file to force this command to run every time, 82 | # since currently there's no way to get a full input/output list from the 83 | # flutter tool. 84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 86 | add_custom_command( 87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 89 | ${CPP_WRAPPER_SOURCES_APP} 90 | ${PHONY_OUTPUT} 91 | COMMAND ${CMAKE_COMMAND} -E env 92 | ${FLUTTER_TOOL_ENVIRONMENT} 93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 94 | windows-x64 $ 95 | VERBATIM 96 | ) 97 | add_custom_target(flutter_assemble DEPENDS 98 | "${FLUTTER_LIBRARY}" 99 | ${FLUTTER_LIBRARY_HEADERS} 100 | ${CPP_WRAPPER_SOURCES_CORE} 101 | ${CPP_WRAPPER_SOURCES_PLUGIN} 102 | ${CPP_WRAPPER_SOURCES_APP} 103 | ) 104 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void RegisterPlugins(flutter::PluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | set(PLUGIN_BUNDLED_LIBRARIES) 9 | 10 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 11 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 12 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 13 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 14 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 15 | endforeach(plugin) 16 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "run_loop.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "A new Flutter project." "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "example.exe" "\0" 98 | VALUE "ProductName", "example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(RunLoop* run_loop, 8 | const flutter::DartProject& project) 9 | : run_loop_(run_loop), project_(project) {} 10 | 11 | FlutterWindow::~FlutterWindow() {} 12 | 13 | bool FlutterWindow::OnCreate() { 14 | if (!Win32Window::OnCreate()) { 15 | return false; 16 | } 17 | 18 | RECT frame = GetClientArea(); 19 | 20 | // The size here must match the window dimensions to avoid unnecessary surface 21 | // creation / destruction in the startup path. 22 | flutter_controller_ = std::make_unique( 23 | frame.right - frame.left, frame.bottom - frame.top, project_); 24 | // Ensure that basic setup of the controller was successful. 25 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 26 | return false; 27 | } 28 | RegisterPlugins(flutter_controller_->engine()); 29 | run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); 30 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 31 | return true; 32 | } 33 | 34 | void FlutterWindow::OnDestroy() { 35 | if (flutter_controller_) { 36 | run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); 37 | flutter_controller_ = nullptr; 38 | } 39 | 40 | Win32Window::OnDestroy(); 41 | } 42 | 43 | LRESULT 44 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 45 | WPARAM const wparam, 46 | LPARAM const lparam) noexcept { 47 | // Give Flutter, including plugins, an opportunity to handle window messages. 48 | if (flutter_controller_) { 49 | std::optional result = 50 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 51 | lparam); 52 | if (result) { 53 | return *result; 54 | } 55 | } 56 | 57 | switch (message) { 58 | case WM_FONTCHANGE: 59 | flutter_controller_->engine()->ReloadSystemFonts(); 60 | break; 61 | } 62 | 63 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "run_loop.h" 10 | #include "win32_window.h" 11 | 12 | // A window that does nothing but host a Flutter view. 13 | class FlutterWindow : public Win32Window { 14 | public: 15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a 16 | // Flutter view running |project|. 17 | explicit FlutterWindow(RunLoop* run_loop, 18 | const flutter::DartProject& project); 19 | virtual ~FlutterWindow(); 20 | 21 | protected: 22 | // Win32Window: 23 | bool OnCreate() override; 24 | void OnDestroy() override; 25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 26 | LPARAM const lparam) noexcept override; 27 | 28 | private: 29 | // The run loop driving events for this window. 30 | RunLoop* run_loop_; 31 | 32 | // The project to run. 33 | flutter::DartProject project_; 34 | 35 | // The Flutter instance hosted by this window. 36 | std::unique_ptr flutter_controller_; 37 | }; 38 | 39 | #endif // RUNNER_FLUTTER_WINDOW_H_ 40 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "run_loop.h" 7 | #include "utils.h" 8 | 9 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 10 | _In_ wchar_t *command_line, _In_ int show_command) { 11 | // Attach to console when present (e.g., 'flutter run') or create a 12 | // new console when running with a debugger. 13 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 14 | CreateAndAttachConsole(); 15 | } 16 | 17 | // Initialize COM, so that it is available for use in the library and/or 18 | // plugins. 19 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 20 | 21 | RunLoop run_loop; 22 | 23 | flutter::DartProject project(L"data"); 24 | 25 | std::vector command_line_arguments = 26 | GetCommandLineArguments(); 27 | 28 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 29 | 30 | FlutterWindow window(&run_loop, project); 31 | Win32Window::Point origin(10, 10); 32 | Win32Window::Size size(1280, 720); 33 | if (!window.CreateAndShow(L"example", origin, size)) { 34 | return EXIT_FAILURE; 35 | } 36 | window.SetQuitOnClose(true); 37 | 38 | run_loop.Run(); 39 | 40 | ::CoUninitialize(); 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flrx/validator/bec95620f3f47fdbd0328b6cb67c4f6a64f30b96/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/run_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "run_loop.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | RunLoop::RunLoop() {} 8 | 9 | RunLoop::~RunLoop() {} 10 | 11 | void RunLoop::Run() { 12 | bool keep_running = true; 13 | TimePoint next_flutter_event_time = TimePoint::clock::now(); 14 | while (keep_running) { 15 | std::chrono::nanoseconds wait_duration = 16 | std::max(std::chrono::nanoseconds(0), 17 | next_flutter_event_time - TimePoint::clock::now()); 18 | ::MsgWaitForMultipleObjects( 19 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), 20 | QS_ALLINPUT); 21 | bool processed_events = false; 22 | MSG message; 23 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects 24 | // won't return again for items left in the queue after PeekMessage. 25 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { 26 | processed_events = true; 27 | if (message.message == WM_QUIT) { 28 | keep_running = false; 29 | break; 30 | } 31 | ::TranslateMessage(&message); 32 | ::DispatchMessage(&message); 33 | // Allow Flutter to process messages each time a Windows message is 34 | // processed, to prevent starvation. 35 | next_flutter_event_time = 36 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 37 | } 38 | // If the PeekMessage loop didn't run, process Flutter messages. 39 | if (!processed_events) { 40 | next_flutter_event_time = 41 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 42 | } 43 | } 44 | } 45 | 46 | void RunLoop::RegisterFlutterInstance( 47 | flutter::FlutterEngine* flutter_instance) { 48 | flutter_instances_.insert(flutter_instance); 49 | } 50 | 51 | void RunLoop::UnregisterFlutterInstance( 52 | flutter::FlutterEngine* flutter_instance) { 53 | flutter_instances_.erase(flutter_instance); 54 | } 55 | 56 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { 57 | TimePoint next_event_time = TimePoint::max(); 58 | for (auto instance : flutter_instances_) { 59 | std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); 60 | if (wait_duration != std::chrono::nanoseconds::max()) { 61 | next_event_time = 62 | std::min(next_event_time, TimePoint::clock::now() + wait_duration); 63 | } 64 | } 65 | return next_event_time; 66 | } 67 | -------------------------------------------------------------------------------- /example/windows/runner/run_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_RUN_LOOP_H_ 2 | #define RUNNER_RUN_LOOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // A runloop that will service events for Flutter instances as well 10 | // as native messages. 11 | class RunLoop { 12 | public: 13 | RunLoop(); 14 | ~RunLoop(); 15 | 16 | // Prevent copying 17 | RunLoop(RunLoop const&) = delete; 18 | RunLoop& operator=(RunLoop const&) = delete; 19 | 20 | // Runs the run loop until the application quits. 21 | void Run(); 22 | 23 | // Registers the given Flutter instance for event servicing. 24 | void RegisterFlutterInstance( 25 | flutter::FlutterEngine* flutter_instance); 26 | 27 | // Unregisters the given Flutter instance from event servicing. 28 | void UnregisterFlutterInstance( 29 | flutter::FlutterEngine* flutter_instance); 30 | 31 | private: 32 | using TimePoint = std::chrono::steady_clock::time_point; 33 | 34 | // Processes all currently pending messages for registered Flutter instances. 35 | TimePoint ProcessFlutterMessages(); 36 | 37 | std::set flutter_instances_; 38 | }; 39 | 40 | #endif // RUNNER_RUN_LOOP_H_ 41 | -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | 5 | #include "resource.h" 6 | 7 | namespace { 8 | 9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 10 | 11 | // The number of Win32Window objects that currently exist. 12 | static int g_active_window_count = 0; 13 | 14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 15 | 16 | // Scale helper to convert logical scaler values to physical using passed in 17 | // scale factor 18 | int Scale(int source, double scale_factor) { 19 | return static_cast(source * scale_factor); 20 | } 21 | 22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 23 | // This API is only needed for PerMonitor V1 awareness mode. 24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 25 | HMODULE user32_module = LoadLibraryA("User32.dll"); 26 | if (!user32_module) { 27 | return; 28 | } 29 | auto enable_non_client_dpi_scaling = 30 | reinterpret_cast( 31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 32 | if (enable_non_client_dpi_scaling != nullptr) { 33 | enable_non_client_dpi_scaling(hwnd); 34 | FreeLibrary(user32_module); 35 | } 36 | } 37 | 38 | } // namespace 39 | 40 | // Manages the Win32Window's window class registration. 41 | class WindowClassRegistrar { 42 | public: 43 | ~WindowClassRegistrar() = default; 44 | 45 | // Returns the singleton registar instance. 46 | static WindowClassRegistrar* GetInstance() { 47 | if (!instance_) { 48 | instance_ = new WindowClassRegistrar(); 49 | } 50 | return instance_; 51 | } 52 | 53 | // Returns the name of the window class, registering the class if it hasn't 54 | // previously been registered. 55 | const wchar_t* GetWindowClass(); 56 | 57 | // Unregisters the window class. Should only be called if there are no 58 | // instances of the window. 59 | void UnregisterWindowClass(); 60 | 61 | private: 62 | WindowClassRegistrar() = default; 63 | 64 | static WindowClassRegistrar* instance_; 65 | 66 | bool class_registered_ = false; 67 | }; 68 | 69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 70 | 71 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 72 | if (!class_registered_) { 73 | WNDCLASS window_class{}; 74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | window_class.lpszClassName = kWindowClassName; 76 | window_class.style = CS_HREDRAW | CS_VREDRAW; 77 | window_class.cbClsExtra = 0; 78 | window_class.cbWndExtra = 0; 79 | window_class.hInstance = GetModuleHandle(nullptr); 80 | window_class.hIcon = 81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 82 | window_class.hbrBackground = 0; 83 | window_class.lpszMenuName = nullptr; 84 | window_class.lpfnWndProc = Win32Window::WndProc; 85 | RegisterClass(&window_class); 86 | class_registered_ = true; 87 | } 88 | return kWindowClassName; 89 | } 90 | 91 | void WindowClassRegistrar::UnregisterWindowClass() { 92 | UnregisterClass(kWindowClassName, nullptr); 93 | class_registered_ = false; 94 | } 95 | 96 | Win32Window::Win32Window() { 97 | ++g_active_window_count; 98 | } 99 | 100 | Win32Window::~Win32Window() { 101 | --g_active_window_count; 102 | Destroy(); 103 | } 104 | 105 | bool Win32Window::CreateAndShow(const std::wstring& title, 106 | const Point& origin, 107 | const Size& size) { 108 | Destroy(); 109 | 110 | const wchar_t* window_class = 111 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 112 | 113 | const POINT target_point = {static_cast(origin.x), 114 | static_cast(origin.y)}; 115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 117 | double scale_factor = dpi / 96.0; 118 | 119 | HWND window = CreateWindow( 120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 123 | nullptr, nullptr, GetModuleHandle(nullptr), this); 124 | 125 | if (!window) { 126 | return false; 127 | } 128 | 129 | return OnCreate(); 130 | } 131 | 132 | // static 133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 134 | UINT const message, 135 | WPARAM const wparam, 136 | LPARAM const lparam) noexcept { 137 | if (message == WM_NCCREATE) { 138 | auto window_struct = reinterpret_cast(lparam); 139 | SetWindowLongPtr(window, GWLP_USERDATA, 140 | reinterpret_cast(window_struct->lpCreateParams)); 141 | 142 | auto that = static_cast(window_struct->lpCreateParams); 143 | EnableFullDpiSupportIfAvailable(window); 144 | that->window_handle_ = window; 145 | } else if (Win32Window* that = GetThisFromHandle(window)) { 146 | return that->MessageHandler(window, message, wparam, lparam); 147 | } 148 | 149 | return DefWindowProc(window, message, wparam, lparam); 150 | } 151 | 152 | LRESULT 153 | Win32Window::MessageHandler(HWND hwnd, 154 | UINT const message, 155 | WPARAM const wparam, 156 | LPARAM const lparam) noexcept { 157 | switch (message) { 158 | case WM_DESTROY: 159 | window_handle_ = nullptr; 160 | Destroy(); 161 | if (quit_on_close_) { 162 | PostQuitMessage(0); 163 | } 164 | return 0; 165 | 166 | case WM_DPICHANGED: { 167 | auto newRectSize = reinterpret_cast(lparam); 168 | LONG newWidth = newRectSize->right - newRectSize->left; 169 | LONG newHeight = newRectSize->bottom - newRectSize->top; 170 | 171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 173 | 174 | return 0; 175 | } 176 | case WM_SIZE: { 177 | RECT rect = GetClientArea(); 178 | if (child_content_ != nullptr) { 179 | // Size and position the child window. 180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 181 | rect.bottom - rect.top, TRUE); 182 | } 183 | return 0; 184 | } 185 | 186 | case WM_ACTIVATE: 187 | if (child_content_ != nullptr) { 188 | SetFocus(child_content_); 189 | } 190 | return 0; 191 | } 192 | 193 | return DefWindowProc(window_handle_, message, wparam, lparam); 194 | } 195 | 196 | void Win32Window::Destroy() { 197 | OnDestroy(); 198 | 199 | if (window_handle_) { 200 | DestroyWindow(window_handle_); 201 | window_handle_ = nullptr; 202 | } 203 | if (g_active_window_count == 0) { 204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 205 | } 206 | } 207 | 208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 209 | return reinterpret_cast( 210 | GetWindowLongPtr(window, GWLP_USERDATA)); 211 | } 212 | 213 | void Win32Window::SetChildContent(HWND content) { 214 | child_content_ = content; 215 | SetParent(content, window_handle_); 216 | RECT frame = GetClientArea(); 217 | 218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 219 | frame.bottom - frame.top, true); 220 | 221 | SetFocus(child_content_); 222 | } 223 | 224 | RECT Win32Window::GetClientArea() { 225 | RECT frame; 226 | GetClientRect(window_handle_, &frame); 227 | return frame; 228 | } 229 | 230 | HWND Win32Window::GetHandle() { 231 | return window_handle_; 232 | } 233 | 234 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 235 | quit_on_close_ = quit_on_close; 236 | } 237 | 238 | bool Win32Window::OnCreate() { 239 | // No-op; provided for subclasses. 240 | return true; 241 | } 242 | 243 | void Win32Window::OnDestroy() { 244 | // No-op; provided for subclasses. 245 | } 246 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /lib/flrx_validator.dart: -------------------------------------------------------------------------------- 1 | export 'package:flrx_validator/src/rules/any_rule.dart'; 2 | export 'package:flrx_validator/src/rules/each_rule.dart'; 3 | export 'package:flrx_validator/src/rules/email_rule.dart'; 4 | export 'package:flrx_validator/src/rules/in_rule.dart'; 5 | export 'package:flrx_validator/src/rules/max_length_rule.dart'; 6 | export 'package:flrx_validator/src/rules/min_length_rule.dart'; 7 | export 'package:flrx_validator/src/rules/not_in_rule.dart'; 8 | export 'package:flrx_validator/src/rules/regex_rule.dart'; 9 | export 'package:flrx_validator/src/rules/required_rule.dart'; 10 | export 'package:flrx_validator/src/rules/rule.dart'; 11 | export 'package:flrx_validator/src/validator.dart'; 12 | -------------------------------------------------------------------------------- /lib/src/rules/any_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/rule.dart'; 2 | 3 | /// A [Rule] subclass which validates if any of the list of [Rule] 4 | /// passed to it are passing. 5 | class AnyRule extends Rule { 6 | /// The list of [Rule] to be validated for. 7 | final List> _ruleList; 8 | 9 | /// The default constructor 10 | AnyRule(this._ruleList, {String? validationMessage}) 11 | : super(validationMessage); 12 | 13 | @override 14 | String? onValidate(String entityName, T? value) { 15 | String? ruleValidationMessage; 16 | _ruleList.reversed.any((Rule rule) { 17 | rule.transformMessage ??= transformMessage; 18 | ruleValidationMessage = rule.validate(entityName, value); 19 | return ruleValidationMessage == null; 20 | }); 21 | return ruleValidationMessage; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/rules/each_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/rule.dart'; 2 | 3 | /// A [Rule] subclass which runs validation on the list of [Rule] 4 | /// and does not stop if any of them fail. 5 | class EachRule extends Rule { 6 | /// The list of [Rule] to be validated for. 7 | final List> _ruleList; 8 | 9 | /// A function which concatenates all the Validation Messages. 10 | final String Function(List) concatenator; 11 | 12 | /// The default constructor 13 | EachRule( 14 | this._ruleList, { 15 | String? validationMessage, 16 | this.concatenator = joinWithNewLine, 17 | }) : super(validationMessage); 18 | 19 | static String joinWithNewLine(List validationMessages) { 20 | return validationMessages.where((element) { 21 | return element != null; 22 | }).join('\n'); 23 | } 24 | 25 | @override 26 | String? onValidate(String entityName, T? value) { 27 | var eachRulesValidationMessage = _ruleList 28 | .map((Rule rule) { 29 | rule.transformMessage ??= transformMessage; 30 | return rule.validate(entityName, value); 31 | }) 32 | .where((String? validationMessage) => validationMessage != null) 33 | .toList(); 34 | 35 | if (eachRulesValidationMessage.isEmpty) { 36 | return null; 37 | } 38 | 39 | return concatenator(eachRulesValidationMessage); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/rules/email_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/regex_rule.dart'; 2 | 3 | /// A [Rule] subclass validating if the given input is a valid email. 4 | class EmailRule extends RegexRule { 5 | static String get emailRegex => 6 | r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$"; 7 | 8 | static String get _emailValidationMessage => 9 | ':value is not a valid email address'; 10 | 11 | EmailRule({String? validationMessage}) 12 | : super(emailRegex, 13 | validationMessage: validationMessage ?? _emailValidationMessage); 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/rules/in_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/rule.dart'; 2 | 3 | /// A [Rule] subclass validating if the input is one of the element in the [acceptedList]. 4 | class InRule extends Rule { 5 | final List acceptedList; 6 | 7 | InRule(this.acceptedList, {String? validationMessage}) 8 | : super(validationMessage); 9 | 10 | @override 11 | String? onValidate(String entityName, T? value) { 12 | var validationMessage = ':entity is not in list of accepted values'; 13 | return acceptedList.contains(value) ? null : validationMessage; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/rules/max_length_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/rule.dart'; 2 | 3 | /// A [Rule] subclass validating if the length of input is less than [maxLength]. 4 | class MaxLengthRule extends Rule { 5 | final int maxLength; 6 | 7 | MaxLengthRule(this.maxLength, {String? validationMessage}) 8 | : assert(T == List || T == Map || T == String || T == dynamic), 9 | super(validationMessage); 10 | 11 | @override 12 | String? onValidate(String entityName, T? value) { 13 | if (value == null) { 14 | return null; 15 | } 16 | 17 | if (value.length <= maxLength) { 18 | return null; 19 | } 20 | 21 | return validationMessage ?? ':entity length should be less than :maxLength'; 22 | } 23 | 24 | @override 25 | Map getRuleSpecificParams() { 26 | return {'maxLength': maxLength.toString()}; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/rules/max_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/number_rule.dart'; 2 | 3 | /// A [Rule] subclass which validates if the value 4 | /// passed is at most a specific number. 5 | class MaxRule extends NumberRule { 6 | /// Value passed should be at most [maxValue] 7 | final num maxValue; 8 | 9 | /// The default constructor 10 | MaxRule( 11 | this.maxValue, { 12 | String? validationMessage, 13 | }) : super(validationMessage: validationMessage); 14 | 15 | @override 16 | String? onValidate(String entityName, T? value) { 17 | var message = super.onValidate(entityName, value); 18 | if (message != null) { 19 | return message; 20 | } 21 | 22 | var numValue = double.parse(value.toString()); 23 | if (numValue > maxValue) { 24 | return '$entityName should be lesser than $maxValue'; 25 | } 26 | 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/rules/min_length_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/rule.dart'; 2 | 3 | /// A [Rule] subclass validating if the input length is more than [minLength]. 4 | class MinLengthRule extends Rule { 5 | final int minLength; 6 | 7 | MinLengthRule(this.minLength, {String? validationMessage}) 8 | : assert(T == List || T == Map || T == String || T == dynamic), 9 | super(validationMessage); 10 | 11 | @override 12 | String? onValidate(String entityName, T? value) { 13 | if (value == null) { 14 | return null; 15 | } 16 | 17 | if (value.length < minLength) { 18 | return ':entity length should be more than :minLength'; 19 | } 20 | return null; 21 | } 22 | 23 | @override 24 | Map getRuleSpecificParams() { 25 | return {'minLength': minLength.toString()}; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/rules/min_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/number_rule.dart'; 2 | 3 | /// A [Rule] subclass which validates if the value 4 | /// passed is at least a specific number. 5 | class MinRule extends NumberRule { 6 | /// Value passed should be at least [minValue] 7 | final num minValue; 8 | 9 | MinRule( 10 | this.minValue, { 11 | String? validationMessage, 12 | }) : super(validationMessage: validationMessage); 13 | 14 | @override 15 | String? onValidate(String entityName, T? value) { 16 | var message = super.onValidate(entityName, value); 17 | if (message != null) { 18 | return message; 19 | } 20 | 21 | var numValue = double.parse(value.toString()); 22 | if (numValue < minValue) { 23 | return '$entityName should be greater than $minValue'; 24 | } 25 | 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/rules/not_in_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/rule.dart'; 2 | 3 | /// A [Rule] subclass validating if the input is one of the element in the [rejectionList]. 4 | class NotInRule extends Rule { 5 | final List rejectionList; 6 | 7 | NotInRule(this.rejectionList, {String? validationMessage}) 8 | : super(validationMessage); 9 | 10 | @override 11 | String? onValidate(String entityName, T? value) { 12 | var validationMessage = ':entity is in list of rejected values'; 13 | return rejectionList.contains(value) ? validationMessage : null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/rules/number_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | 3 | /// A [Rule] subclass which validates if the value passed is a number. 4 | class NumberRule extends Rule { 5 | /// The default constructor 6 | NumberRule({String? validationMessage}) : super(validationMessage); 7 | 8 | @override 9 | String? onValidate(String entityName, T? value) { 10 | num? numValue; 11 | if (value is! num) { 12 | numValue = double.tryParse(value.toString()); 13 | } 14 | if (numValue == null) { 15 | return '$entityName is not a number'; 16 | } 17 | return null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/rules/regex_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/rule.dart'; 2 | 3 | /// A [Rule] subclass validating the input matches a [regex]. 4 | class RegexRule extends Rule { 5 | RegexRule(this.regex, {String? validationMessage}) : super(validationMessage); 6 | 7 | final String regex; 8 | 9 | @override 10 | String? onValidate(String entityName, String? value) { 11 | var regExp = RegExp(regex); 12 | if (!regExp.hasMatch(value!)) { 13 | return ':value is not a valid pattern'; 14 | } 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/rules/required_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/rule.dart'; 2 | 3 | /// A [Rule] subclass validating if the input is notEmpty. 4 | /// If the Value is null, this rule fails. 5 | /// If the value if of type String and if the String is empty, this rule fails. 6 | /// If the value if of type Iterable or Map and if the value is empty, this rule fails. 7 | class RequiredRule extends Rule { 8 | RequiredRule({String? validationMessage}) : super(validationMessage); 9 | 10 | @override 11 | String? onValidate(String entityName, T? value) { 12 | if (value == null || 13 | isEmptyIterable(value) || 14 | isEmptyMap(value) || 15 | isEmptyString(value)) { 16 | return ':entity is required.'; 17 | } 18 | return null; 19 | } 20 | 21 | bool isEmptyString(value) => value is String && value.isEmpty; 22 | 23 | bool isEmptyMap(value) => value is Map && value.isEmpty; 24 | 25 | bool isEmptyIterable(value) => value is Iterable && value.isEmpty; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/rules/rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/validator.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | /// An abstract class designed to use with [Validator]. 5 | /// 6 | /// All the rules must extend this class as it specifies the blueprint 7 | /// on how the [Validator] is expecting the [Rule] classes to be. 8 | abstract class Rule { 9 | /// The default constructor, taking custom [validationMessage]. 10 | Rule(this.validationMessage); 11 | 12 | /// A custom validation message to be used with the rule. 13 | /// 14 | /// This can be passed to the default constructor if the user wants to 15 | /// show a custom validation message over the default ones for the rule. 16 | final String? validationMessage; 17 | 18 | /// A function used for transforming the message that comes back from the rules. 19 | /// 20 | /// This function can generally be used when we need to localize the message 21 | /// which comes back from the rules. 22 | /// 23 | /// If passed to [Rule], it overrides the default [Validator.transformMessage] 24 | /// function. 25 | MessageTransformer? transformMessage; 26 | 27 | /// Returns validation message if validation fails. 28 | /// 29 | /// If [Validator.transformMessage] or [transformMessage] is passed, it returns 30 | /// transformed validated message back to the user. 31 | /// 32 | /// Override it on the child class when necessary. 33 | String? validate(String entityName, T? value) { 34 | var validationMessageFromRule = onValidate(entityName, value); 35 | if (validationMessageFromRule == null) { 36 | return null; 37 | } 38 | return transformMessage!(validationMessage ?? validationMessageFromRule, 39 | _getResultantMap(entityName, value)); 40 | } 41 | 42 | /// Returns the validation message by the child class extending [Rule]. 43 | /// 44 | /// Override it in the child class to define the validation logic for it. 45 | @protected 46 | String? onValidate(String entityName, T? value); 47 | 48 | /// Returns a Map of values that are needed to be replaced in 49 | /// [validationMessage]. 50 | /// 51 | /// This function is takes the default entity and value along with 52 | /// the key, value pairs provided by the [getRuleSpecificParams()] function. 53 | Map _getResultantMap(String entityName, T? value) { 54 | return { 55 | 'entity': entityName, 56 | 'value': value.toString(), 57 | ...getRuleSpecificParams() 58 | }; 59 | } 60 | 61 | /// Returns a Map of values that are specific to the child [Rule]. 62 | Map getRuleSpecificParams() => {}; 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/utils/string_utils.dart: -------------------------------------------------------------------------------- 1 | class StringUtils { 2 | static String replaceWithValues(String string, Map valueMap) { 3 | return string.replaceAllMapped(RegExp('(:[a-zA-Z0-9_]+)'), (Match match) { 4 | if (match.groupCount <= 0) { 5 | return ''; 6 | } 7 | var key = match.group(0)!.substring(1); 8 | var value = valueMap[key]; 9 | ArgumentError.checkNotNull(value, key); 10 | return value!; 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/validator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/rule.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | 4 | /// A class primarily designed to retrieve validation messages based on [Rule]s. 5 | /// 6 | /// The [Validator] class takes a List of [Rule]. The value supplied to the 7 | /// [Validator] is supplied to the [Rule]s in the order they were registered. 8 | /// The error message of the first [Rule] that fails is returned back. 9 | /// If all the [Rule]s pass, then null is returned. 10 | class Validator { 11 | Validator({ 12 | required List> rules, 13 | this.entityName = 'Entity', 14 | this.transformMessage = StringUtils.replaceWithValues, 15 | }) { 16 | rulesList = rules; 17 | } 18 | 19 | /// The name of the field that is being evaluated. 20 | /// 21 | /// The [Validator.entityName] property is used by some [Rule]s to format their 22 | /// Error Message. 23 | String entityName; 24 | 25 | /// A function used for transforming the message that comes back from the rules. 26 | /// 27 | /// This function can generally be used when we need to localize the message 28 | /// which comes back from the rules. 29 | MessageTransformer transformMessage; 30 | 31 | /// List of all registered rules. 32 | List> rulesList = >[]; 33 | 34 | /// Registers a single [Rule] with the [Validator]. 35 | Validator add(Rule rule) { 36 | rulesList.add(rule); 37 | return this; 38 | } 39 | 40 | /// Registers a List of [Rule] with the [Validator]. 41 | Validator addAll(List> rule) { 42 | rulesList.addAll(rule); 43 | return this; 44 | } 45 | 46 | String? call(T? value) => validate(value); 47 | 48 | /// Validates and returns an error message(if any). 49 | String? validate(T? value) { 50 | String? validationMessage; 51 | rulesList.any((Rule rule) { 52 | rule.transformMessage ??= transformMessage; 53 | validationMessage = rule.validate(entityName, value); 54 | return validationMessage != null; 55 | }); 56 | return validationMessage; 57 | } 58 | } 59 | 60 | typedef MessageTransformer = String Function( 61 | String message, 62 | Map valueMapping, 63 | ); 64 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flrx_validator 2 | description: > 3 | A powerful, extensible validator package to get validation messages based on a list of rules. 4 | version: 0.6.0 5 | homepage: https://flrx.github.io/validator/ 6 | repository: https://github.com/flrx/validator 7 | issue_tracker: https://github.com/flrx/validator/issues 8 | documentation: https://flrx.github.io/validator/ 9 | 10 | environment: 11 | sdk: ">=2.12.0 <3.0.0" 12 | 13 | dependencies: 14 | meta: ^1.3.0 15 | 16 | dev_dependencies: 17 | lints: ^1.0.1 18 | test: ^1.16.8 19 | -------------------------------------------------------------------------------- /test/mocks/mock_validator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | 3 | class MockValidator extends Validator { 4 | MockValidator() : super(rules: []); 5 | 6 | @override 7 | Validator addAll(List> rule) { 8 | rulesList.addAll(rule); 9 | return this; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/mocks/mock_validator_rule.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | 3 | class MockRule extends Rule { 4 | MockRule({String? validationMessage}) : super(validationMessage); 5 | 6 | @override 7 | String? onValidate(String entityName, String? value) => value; 8 | 9 | @override 10 | Map getRuleSpecificParams() => 11 | {'mockParam': 'mockParamValue'}; 12 | } 13 | 14 | class MockAlwaysFailRule extends Rule { 15 | MockAlwaysFailRule() : super(null); 16 | 17 | @override 18 | String onValidate(String entityName, dynamic value) { 19 | return '$value is not a valid value for $entityName'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/unit/rules/any_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import '../../mocks/mock_validator_rule.dart'; 6 | 7 | void main() { 8 | var entityName = 'any'; 9 | String? validForMockRule; 10 | var invalidForMockRule = 'invalid'; 11 | 12 | var rule = AnyRule(>[ 13 | MockRule(), 14 | MockAlwaysFailRule(), 15 | ]); 16 | 17 | rule.transformMessage = StringUtils.replaceWithValues; 18 | 19 | test('valid_any_rule_test', () { 20 | var validationError = rule.validate(entityName, validForMockRule); 21 | expect(validationError, validForMockRule); 22 | }); 23 | 24 | test('invalid_any_rule_test', () { 25 | var validationError = rule.validate(entityName, invalidForMockRule); 26 | expect(validationError, invalidForMockRule); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /test/unit/rules/each_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | group('EachRule', () { 7 | Rule rule = EachRule( 8 | >[ 9 | MinLengthRule(6), 10 | RegexRule( 11 | r'(?=.*[a-z])', 12 | validationMessage: ':entity should contain one lowercase character', 13 | ), 14 | ], 15 | ); 16 | rule.transformMessage = StringUtils.replaceWithValues; 17 | 18 | test('Runs all rules', () { 19 | var message = rule.validate('Password', '123'); 20 | expect(message, 21 | 'Password length should be more than 6\nPassword should contain one lowercase character'); 22 | }); 23 | 24 | test('Validated message exists only for rule that fails', () { 25 | var message = rule.validate('Password', 'abcd'); 26 | expect(message, 'Password length should be more than 6'); 27 | }); 28 | 29 | test('Validated message is null when all rules pass', () { 30 | var message = rule.validate('Password', '1234abcd'); 31 | expect(message, null); 32 | }); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /test/unit/rules/email_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | // Test Cases are from Microsoft, 6 | // https://blogs.msdn.microsoft.com/testing123/2009/02/06/email-address-test-cases/ 7 | void main() { 8 | var entityName = 'email'; 9 | var validEmailIds = [ 10 | 'abcd@example.com', 11 | 'firstname.lastname@domain.com', 12 | 'email@subdomain.domain.com', 13 | 'firstname+lastname@domain.com', 14 | 'email@123.123.123.123', 15 | '1234567890@domain.com', 16 | 'email@domain-one.com', 17 | '_______@domain.com', 18 | 'email@domain.name', 19 | 'email@domain.co.jp', 20 | 'firstname-lastname@domain.com', 21 | // TODO These 2 cases should pass 22 | // 'email@[123.123.123.123]', 23 | // '"email"@domain.com', 24 | ]; 25 | var invalidEmailIds = [ 26 | 'plainaddress', 27 | 'abcd@example,com', 28 | r'#@%^%#$@#$@#.com', 29 | '@domain.com', 30 | 'Joe Smith ', 31 | 'email.domain.com', 32 | 'email@domain@domain.com', 33 | 'あいうえお@domain.com', 34 | 'email@domain.com (Joe Smith)', 35 | 'email@-domain.com', 36 | 'email@domain..com', 37 | // TODO The following test cases should pass 38 | // '.email@domain.com', 39 | // 'email.@domain.com', 40 | // 'email..email@domain.com', 41 | // 'email@domain', 42 | ]; 43 | 44 | var rule = EmailRule(); 45 | rule.transformMessage = StringUtils.replaceWithValues; 46 | 47 | test('valid_email_test', () { 48 | for (var emailId in validEmailIds) { 49 | var validationError = rule.validate(entityName, emailId); 50 | expect(validationError, null); 51 | } 52 | }); 53 | 54 | test('invalid_email_test', () { 55 | for (var emailId in invalidEmailIds) { 56 | var validationError = rule.validate(entityName, emailId); 57 | expect(validationError, '$emailId is not a valid email address'); 58 | } 59 | }); 60 | 61 | test('empty_email_test', () { 62 | var validationError = rule.validate(entityName, ''); 63 | expect(validationError, ' is not a valid email address'); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /test/unit/rules/in_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | var entityName = 'In'; 7 | var validString = 'test1'; 8 | var invalidString = 'fail'; 9 | 10 | var rule = InRule(['test1', 'test2']); 11 | rule.transformMessage = StringUtils.replaceWithValues; 12 | 13 | test('valid_one_of_test', () { 14 | var validationError = rule.validate(entityName, validString); 15 | expect(validationError, null); 16 | }); 17 | 18 | test('invalid_one_of_test', () { 19 | var validationError = rule.validate(entityName, invalidString); 20 | expect(validationError, '$entityName is not in list of accepted values'); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/unit/rules/max_length_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | group('String Max Length Rule Test', () { 7 | var entityName = 'maxLength'; 8 | var maxLength = 5; 9 | var validString = '12345'; 10 | var invalidString = '123456'; 11 | String? nullString; 12 | 13 | var rule = MaxLengthRule(maxLength); 14 | rule.transformMessage = StringUtils.replaceWithValues; 15 | 16 | test('valid_max_length_test', () { 17 | var validationError = rule.validate(entityName, validString); 18 | expect(validationError, null); 19 | }); 20 | 21 | test('invalid_max_length_test', () { 22 | var validationError = rule.validate(entityName, invalidString); 23 | expect( 24 | validationError, '$entityName length should be less than $maxLength'); 25 | }); 26 | 27 | test('null_string_length_test', () { 28 | var validationError = rule.validate(entityName, nullString); 29 | expect(validationError, null); 30 | }); 31 | }); 32 | 33 | group('List Max Length Rule Test', () { 34 | var entityName = 'maxLength'; 35 | var maxLength = 2; 36 | var validString = [1, 2]; 37 | var invalidString = [1, 2, 3, 4, 5]; 38 | 39 | var rule = MaxLengthRule(maxLength); 40 | rule.transformMessage = StringUtils.replaceWithValues; 41 | 42 | test('valid_max_length_test', () { 43 | var validationError = rule.validate(entityName, validString); 44 | expect(validationError, null); 45 | }); 46 | 47 | test('invalid_max_length_test', () { 48 | var validationError = rule.validate(entityName, invalidString); 49 | expect( 50 | validationError, '$entityName length should be less than $maxLength'); 51 | }); 52 | }); 53 | 54 | group('Map Max Length Rule Test', () { 55 | var entityName = 'maxLength'; 56 | var maxLength = 1; 57 | var validString = {1: 2}; 58 | var invalidString = {1: 2, 3: 4, 5: 56}; 59 | 60 | var rule = MaxLengthRule(maxLength); 61 | rule.transformMessage = StringUtils.replaceWithValues; 62 | 63 | test('valid_max_length_test', () { 64 | var validationError = rule.validate(entityName, validString); 65 | expect(validationError, null); 66 | }); 67 | 68 | test('invalid_max_length_test', () { 69 | var validationError = rule.validate(entityName, invalidString); 70 | expect( 71 | validationError, '$entityName length should be less than $maxLength'); 72 | }); 73 | }); 74 | 75 | group('Dynamic Max Length Rule Test', () { 76 | var entityName = 'maxLength'; 77 | var maxLength = 2; 78 | var invalidString = '12345'; 79 | var validString = '12'; 80 | 81 | var rule = MaxLengthRule(maxLength); 82 | rule.transformMessage = StringUtils.replaceWithValues; 83 | 84 | test('valid_max_length_test', () { 85 | var validationError = rule.validate(entityName, validString); 86 | expect(validationError, null); 87 | }); 88 | 89 | test('invalid_max_length_test', () { 90 | var validationError = rule.validate(entityName, invalidString); 91 | expect( 92 | validationError, '$entityName length should be less than $maxLength'); 93 | }); 94 | }); 95 | 96 | test('Object Max Length Rule Test', () { 97 | expect(() => MaxLengthRule(3), throwsA(const TypeMatcher())); 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /test/unit/rules/max_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/max_rule.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | group('Max Rule Test', () { 7 | var entityName = 'number'; 8 | var validString = '20'; 9 | var invalidString = '50'; 10 | var maxValue = 40; 11 | 12 | var rule = MaxRule(maxValue); 13 | rule.transformMessage = StringUtils.replaceWithValues; 14 | 15 | test('valid_number_test', () { 16 | var validationError = rule.validate(entityName, validString); 17 | expect(validationError, null); 18 | }); 19 | 20 | test('invalid_number_test', () { 21 | var validationError = rule.validate(entityName, invalidString); 22 | expect(validationError, '$entityName should be lesser than $maxValue'); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /test/unit/rules/min_length_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | group('String Min Length Rule Test', () { 7 | var entityName = 'minLength'; 8 | var minLength = 5; 9 | var validString = '12345'; 10 | var invalidString = '12'; 11 | String? nullString; 12 | 13 | var rule = MinLengthRule(minLength); 14 | rule.transformMessage = StringUtils.replaceWithValues; 15 | 16 | test('valid_min_length_test', () { 17 | var validationError = rule.validate(entityName, validString); 18 | expect(validationError, null); 19 | }); 20 | 21 | test('invalid_min_length_test', () { 22 | var validationError = rule.validate(entityName, invalidString); 23 | expect( 24 | validationError, '$entityName length should be more than $minLength'); 25 | }); 26 | 27 | test('null_string_length_test', () { 28 | var validationError = rule.validate(entityName, nullString); 29 | expect(validationError, null); 30 | }); 31 | }); 32 | group('List Min Length Rule Test', () { 33 | var entityName = 'minLength'; 34 | var minLength = 5; 35 | var validString = [1, 2, 3, 4, 5]; 36 | var invalidString = [1, 2]; 37 | 38 | var rule = MinLengthRule(minLength); 39 | rule.transformMessage = StringUtils.replaceWithValues; 40 | 41 | test('valid_min_length_test', () { 42 | var validationError = rule.validate(entityName, validString); 43 | expect(validationError, null); 44 | }); 45 | 46 | test('invalid_min_length_test', () { 47 | var validationError = rule.validate(entityName, invalidString); 48 | expect( 49 | validationError, '$entityName length should be more than $minLength'); 50 | }); 51 | }); 52 | 53 | group('Map Min Length Rule Test', () { 54 | var entityName = 'minLength'; 55 | var minLength = 3; 56 | var validString = {1: 2, 3: 4, 5: 56}; 57 | var invalidString = {1: 2}; 58 | 59 | var rule = MinLengthRule(minLength); 60 | rule.transformMessage = StringUtils.replaceWithValues; 61 | 62 | test('valid_min_length_test', () { 63 | var validationError = rule.validate(entityName, validString); 64 | expect(validationError, null); 65 | }); 66 | 67 | test('invalid_min_length_test', () { 68 | var validationError = rule.validate(entityName, invalidString); 69 | expect( 70 | validationError, '$entityName length should be more than $minLength'); 71 | }); 72 | }); 73 | 74 | group('Dynamic Min Length Rule Test', () { 75 | var entityName = 'minLength'; 76 | var minLength = 5; 77 | var validString = '12345'; 78 | var invalidString = '12'; 79 | 80 | var rule = MinLengthRule(minLength); 81 | rule.transformMessage = StringUtils.replaceWithValues; 82 | 83 | test('valid_min_length_test', () { 84 | var validationError = rule.validate(entityName, validString); 85 | expect(validationError, null); 86 | }); 87 | 88 | test('invalid_min_length_test', () { 89 | var validationError = rule.validate(entityName, invalidString); 90 | expect( 91 | validationError, '$entityName length should be more than $minLength'); 92 | }); 93 | }); 94 | 95 | test('Object Min Length Rule Test', () { 96 | expect(() => MinLengthRule(3), throwsA(isA())); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /test/unit/rules/min_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/min_rule.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | group('Min Rule Test', () { 7 | var entityName = 'number'; 8 | var validString = '50'; 9 | var invalidString = '20'; 10 | var minValue = 40; 11 | 12 | var rule = MinRule(minValue); 13 | rule.transformMessage = StringUtils.replaceWithValues; 14 | 15 | test('valid_number_test', () { 16 | var validationError = rule.validate(entityName, validString); 17 | expect(validationError, null); 18 | }); 19 | 20 | test('invalid_number_test', () { 21 | var validationError = rule.validate(entityName, invalidString); 22 | expect(validationError, '$entityName should be greater than $minValue'); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /test/unit/rules/not_in_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | var entityName = 'notIn'; 7 | var validString = 'fail'; 8 | var invalidString = 'test1'; 9 | 10 | var rule = NotInRule(['test1', 'test2']); 11 | rule.transformMessage = StringUtils.replaceWithValues; 12 | 13 | test('invalid_not_in_rule_test', () { 14 | var validationError = rule.validate(entityName, validString); 15 | expect(validationError, null); 16 | }); 17 | 18 | test('valid_not_in_rule_test', () { 19 | var validationError = rule.validate(entityName, invalidString); 20 | expect(validationError, '$entityName is in list of rejected values'); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/unit/rules/num_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/rules/number_rule.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | group('Number Rule Test', () { 7 | var entityName = 'number'; 8 | var validString = '12345'; 9 | var invalidString = '12abcd'; 10 | 11 | var rule = NumberRule(); 12 | rule.transformMessage = StringUtils.replaceWithValues; 13 | 14 | test('valid_number_test', () { 15 | var validationError = rule.validate(entityName, validString); 16 | expect(validationError, null); 17 | }); 18 | 19 | test('invalid_number_test', () { 20 | var validationError = rule.validate(entityName, invalidString); 21 | expect(validationError, '$entityName is not a number'); 22 | }); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /test/unit/rules/required_rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:flrx_validator/src/utils/string_utils.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | group('String Required Rule Test', () { 7 | var entityName = 'required'; 8 | var validString = '12'; 9 | var invalidString = ''; 10 | 11 | var rule = RequiredRule(); 12 | rule.transformMessage = StringUtils.replaceWithValues; 13 | 14 | test('valid_require_rule_test', () { 15 | var validationError = rule.validate(entityName, validString); 16 | expect(validationError, null); 17 | }); 18 | 19 | test('invalid_require_rule_test', () { 20 | var validationError = rule.validate(entityName, invalidString); 21 | expect(validationError, '$entityName is required.'); 22 | }); 23 | }); 24 | group('List Required Rule Test', () { 25 | var entityName = 'required'; 26 | var validList = ['12']; 27 | var invalidList = []; 28 | 29 | var rule = RequiredRule(); 30 | rule.transformMessage = StringUtils.replaceWithValues; 31 | 32 | test('valid_require_rule_test', () { 33 | var validationError = rule.validate(entityName, validList); 34 | expect(validationError, null); 35 | }); 36 | 37 | test('invalid_require_rule_test', () { 38 | var validationError = rule.validate(entityName, invalidList); 39 | expect(validationError, '$entityName is required.'); 40 | }); 41 | }); 42 | 43 | group('Map Required Rule Test', () { 44 | var entityName = 'required'; 45 | var validMap = {'12': '24'}; 46 | var invalidMap = {}; 47 | 48 | var rule = RequiredRule(); 49 | rule.transformMessage = StringUtils.replaceWithValues; 50 | 51 | test('valid_require_rule_test', () { 52 | var validationError = rule.validate(entityName, validMap); 53 | expect(validationError, null); 54 | }); 55 | 56 | test('invalid_require_rule_test', () { 57 | var validationError = rule.validate(entityName, invalidMap); 58 | expect(validationError, '$entityName is required.'); 59 | }); 60 | }); 61 | 62 | group('Object Required Rule Test', () { 63 | var entityName = 'required'; 64 | var validObject = Object(); 65 | Object? invalidObject; 66 | 67 | var rule = RequiredRule(); 68 | rule.transformMessage = StringUtils.replaceWithValues; 69 | 70 | test('valid_require_rule_test', () { 71 | var validationError = rule.validate(entityName, validObject); 72 | expect(validationError, null); 73 | }); 74 | 75 | test('invalid_require_rule_test', () { 76 | var validationError = rule.validate(entityName, invalidObject); 77 | expect(validationError, '$entityName is required.'); 78 | }); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /test/unit/rules/rule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/utils/string_utils.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import '../../mocks/mock_validator_rule.dart'; 5 | 6 | void main() { 7 | test('rule_uses_custom_validation_message', () { 8 | var validationMessage = 'Custom Validation'; 9 | var rule = MockRule(validationMessage: validationMessage); 10 | rule.transformMessage = StringUtils.replaceWithValues; 11 | 12 | expect(rule.validate('entity', 'value'), validationMessage); 13 | }); 14 | 15 | test('rule_uses_custom_transformer', () { 16 | var rule = MockRule(); 17 | rule.transformMessage = 18 | (String message, Map params) => message.toUpperCase(); 19 | expect(rule.validate('entity', 'value'), 'VALUE'); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /test/unit/utils/string_utils_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/src/utils/string_utils.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('routes replaceWithValues function test', () { 6 | test('no_param_route_test', () { 7 | var route = '/provider'; 8 | var paramRoute = StringUtils.replaceWithValues(route, {}); 9 | var expectedRoute = '/provider'; 10 | expect(paramRoute, expectedRoute); 11 | }); 12 | 13 | test('one_param_route_test', () { 14 | var route = '/provider/:providerId'; 15 | var paramRoute = StringUtils.replaceWithValues( 16 | route, {'providerId': '1'}); 17 | var expectedRoute = '/provider/1'; 18 | expect(paramRoute, expectedRoute); 19 | }); 20 | 21 | test('multiple_param_route_test', () { 22 | var route = '/provider/:providerId/booking/:bookingId'; 23 | var paramRoute = StringUtils.replaceWithValues( 24 | route, {'providerId': '1', 'bookingId': '2'}); 25 | var expectedRoute = '/provider/1/booking/2'; 26 | expect(paramRoute, expectedRoute); 27 | }); 28 | 29 | test('multiple_continuous_params_route_test', () { 30 | var route = '/provider/:providerId/:bookingId'; 31 | var paramRoute = StringUtils.replaceWithValues( 32 | route, {'providerId': '1', 'bookingId': '2'}); 33 | var expectedRoute = '/provider/1/2'; 34 | expect(paramRoute, expectedRoute); 35 | }); 36 | 37 | test('single_paramname_no_param_route_test', () { 38 | var paramName = 'providerId'; 39 | expect(() { 40 | var route = '/provider/:$paramName'; 41 | return StringUtils.replaceWithValues(route, {}); 42 | }, throwsA(predicate((Error e) { 43 | return e is ArgumentError && 44 | e.toString() == 45 | 'Invalid argument(s) ($paramName): Must not be null'; 46 | }))); 47 | }); 48 | 49 | test('one_param_with_underscore_route_test', () { 50 | var route = '/provider/:provider_id'; 51 | var paramRoute = StringUtils.replaceWithValues( 52 | route, {'provider_id': '1'}); 53 | var expectedRoute = '/provider/1'; 54 | expect(paramRoute, expectedRoute); 55 | }); 56 | }); 57 | 58 | group('routes replaceWithValues function test', () { 59 | test('no_value_in_sentence', () { 60 | var sentence = 'this is a sentence'; 61 | var replacedSentence = 62 | StringUtils.replaceWithValues(sentence, {}); 63 | var expectedSentence = 'this is a sentence'; 64 | expect(replacedSentence, expectedSentence); 65 | }); 66 | 67 | test('one_value_in_sentence', () { 68 | var sentence = 'this is a sentence with a :value'; 69 | var replacedSentence = StringUtils.replaceWithValues( 70 | sentence, {'value': 'donut'}); 71 | var expectedSentence = 'this is a sentence with a donut'; 72 | expect(replacedSentence, expectedSentence); 73 | }); 74 | 75 | test('one_value_in_beginning_of_sentence', () { 76 | var sentence = ':name, Hello'; 77 | var replacedSentence = StringUtils.replaceWithValues( 78 | sentence, {'name': 'John'}); 79 | var expectedSentence = 'John, Hello'; 80 | expect(replacedSentence, expectedSentence); 81 | }); 82 | 83 | test('one_value_at_end_of_sentence', () { 84 | var sentence = 'Bye :name'; 85 | var replacedSentence = StringUtils.replaceWithValues( 86 | sentence, {'name': 'John'}); 87 | var expectedSentence = 'Bye John'; 88 | expect(replacedSentence, expectedSentence); 89 | }); 90 | 91 | test('two_continous_values_with_numeric_keys', () { 92 | var sentence = ':var1:var2'; 93 | var replacedSentence = StringUtils.replaceWithValues( 94 | sentence, {'var1': 'Hello', 'var2': ' John'}); 95 | var expectedSentence = 'Hello John'; 96 | expect(replacedSentence, expectedSentence); 97 | }); 98 | 99 | test('no_matching_value_in_string', () { 100 | var sentence = 'Car Chase'; 101 | var replacedSentence = StringUtils.replaceWithValues( 102 | sentence, {'var1': 'Hello', 'var2': ' John'}); 103 | var expectedSentence = 'Car Chase'; 104 | expect(replacedSentence, expectedSentence); 105 | }); 106 | 107 | test('no_value_in_map_of_sentence', () { 108 | expect(() { 109 | var sentence = 'Bye :name'; 110 | return StringUtils.replaceWithValues( 111 | sentence, {'coincidence': 'bro'}); 112 | }, throwsA(predicate((Error e) { 113 | return e is ArgumentError && 114 | e.toString() == 'Invalid argument(s) (name): Must not be null'; 115 | }))); 116 | }); 117 | }); 118 | } 119 | -------------------------------------------------------------------------------- /test/unit/validator/validator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flrx_validator/flrx_validator.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import '../../mocks/mock_validator_rule.dart'; 5 | 6 | void main() { 7 | var mockRule = MockRule(); 8 | var validator = Validator(rules: []); 9 | 10 | setUp(() { 11 | validator = Validator(rules: []); 12 | mockRule = MockRule(); 13 | }); 14 | 15 | group('Validator\'s legacy functions run as expected', () { 16 | test('Validator\'s validate method runs the validator', () { 17 | var validationMessage = validator.add(mockRule).validate(null); 18 | expect(validationMessage, null); 19 | }); 20 | }); 21 | 22 | group('Validator with Rules specified in constructor tests', () { 23 | var customValidator = 24 | Validator(rules: >[RequiredRule(), mockRule]); 25 | test('Validator has the ruleslist initialized with the rules in contructor', 26 | () { 27 | expect(customValidator.rulesList.length, 2); 28 | }); 29 | 30 | test('Can add rules in validator after being initialized with other rules', 31 | () { 32 | customValidator.add(MaxLengthRule(20)); 33 | expect(customValidator.rulesList.length, 3); 34 | }); 35 | }); 36 | 37 | group('validator_rules_tests', () { 38 | test('test_validation_has_all_added_rules', () { 39 | validator.add(mockRule); 40 | expect(validator.rulesList.length, 1); 41 | validator.addAll(>[ 42 | AnyRule(>[]), 43 | InRule([]), 44 | ]); 45 | expect(validator.rulesList.length, 3); 46 | }); 47 | 48 | test('test_validation_rule_passes', () { 49 | var validationFunction = validator.add(mockRule); 50 | var validationMessage = validationFunction(null); 51 | expect(validationMessage, null); 52 | }); 53 | 54 | test('test_validation_rule_fails', () { 55 | Function validationFunction = validator.add(mockRule); 56 | String validationMessage = validationFunction('Fail'); 57 | expect(validationMessage, 'Fail'); 58 | }); 59 | }); 60 | 61 | group('validator_transform_message_tests', () { 62 | String upperCaseTransformer(String message, Map _) => 63 | message.toUpperCase(); 64 | 65 | test('test_validator_with_message_transformer', () { 66 | var customValidator = 67 | Validator(rules: [], transformMessage: upperCaseTransformer); 68 | Function validationFunction = customValidator.add(mockRule); 69 | String validationMessage = validationFunction('value'); 70 | expect(validationMessage, 'VALUE'); 71 | }); 72 | 73 | test('test_rule_uses_custom_transformer', () { 74 | var customValidator = 75 | Validator(rules: [], transformMessage: upperCaseTransformer); 76 | mockRule.transformMessage = 77 | (String message, Map params) => message.toLowerCase(); 78 | Function validationFunction = customValidator.add(mockRule); 79 | String validationMessage = validationFunction('Value'); 80 | expect(validationMessage, 'value'); 81 | }); 82 | 83 | test('test_transform_message_gets_all_params', () { 84 | var entityName = 'MockEntity'; 85 | var valueToValidate = 'Value'; 86 | var mockParamValue = 'mockParamValue'; 87 | var customValidator = Validator( 88 | rules: [], 89 | entityName: entityName, 90 | transformMessage: (String message, Map params) { 91 | expect(params['entity'], entityName); 92 | expect(params['value'], valueToValidate); 93 | expect(params['mockParam'], mockParamValue); 94 | return message.toUpperCase(); 95 | }); 96 | Function validationFunction = customValidator.add(mockRule); 97 | validationFunction(valueToValidate); 98 | }); 99 | }); 100 | } 101 | --------------------------------------------------------------------------------