diff --git a/flutter/digit-ui-components/README.md b/flutter/digit-ui-components/README.md deleted file mode 100644 index 394d88fb5fa..00000000000 --- a/flutter/digit-ui-components/README.md +++ /dev/null @@ -1,24 +0,0 @@ - -# digit ui components - -# Flutter Common UI Widgets - - -## Installation - -Install digit-ui-components with pub - -```bash - $ flutter pub add digit_components - -``` - - - -## License - -[MIT](https://choosealicense.com/licenses/mit/) - - -![Logo](https://s3.ap-south-1.amazonaws.com/works-dev-asset/mseva-white-logo.png) - diff --git a/flutter/digit-ui-components/digit_components/.flutter-plugins b/flutter/digit-ui-components/digit_components/.flutter-plugins new file mode 100644 index 00000000000..be59f0b6940 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/.flutter-plugins @@ -0,0 +1,20 @@ +# This is a generated file; do not edit or check into version control. +flutter_keyboard_visibility=/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility-5.4.1/ +flutter_keyboard_visibility_linux=/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_linux-1.0.0/ +flutter_keyboard_visibility_macos=/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_macos-1.0.0/ +flutter_keyboard_visibility_web=/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_web-2.0.0/ +flutter_keyboard_visibility_windows=/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_windows-1.0.0/ +fluttertoast=/home/admin1/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/ +geolocator=/home/admin1/.pub-cache/hosted/pub.dev/geolocator-10.1.0/ +geolocator_android=/home/admin1/.pub-cache/hosted/pub.dev/geolocator_android-4.4.0/ +geolocator_apple=/home/admin1/.pub-cache/hosted/pub.dev/geolocator_apple-2.3.3/ +geolocator_web=/home/admin1/.pub-cache/hosted/pub.dev/geolocator_web-2.2.0/ +geolocator_windows=/home/admin1/.pub-cache/hosted/pub.dev/geolocator_windows-0.2.2/ +location=/home/admin1/.pub-cache/hosted/pub.dev/location-5.0.3/ +location_web=/home/admin1/.pub-cache/hosted/pub.dev/location_web-4.2.0/ +package_info_plus=/home/admin1/.pub-cache/hosted/pub.dev/package_info_plus-4.2.0/ +path_provider=/home/admin1/.pub-cache/hosted/pub.dev/path_provider-2.1.1/ +path_provider_android=/home/admin1/.pub-cache/hosted/pub.dev/path_provider_android-2.2.2/ +path_provider_foundation=/home/admin1/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/ +path_provider_linux=/home/admin1/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ +path_provider_windows=/home/admin1/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/ diff --git a/flutter/digit-ui-components/digit_components/.flutter-plugins-dependencies b/flutter/digit-ui-components/digit_components/.flutter-plugins-dependencies new file mode 100644 index 00000000000..02fd554941b --- /dev/null +++ b/flutter/digit-ui-components/digit_components/.flutter-plugins-dependencies @@ -0,0 +1 @@ +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_keyboard_visibility","path":"/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility-5.4.1/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/home/admin1/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"geolocator_apple","path":"/home/admin1/.pub-cache/hosted/pub.dev/geolocator_apple-2.3.3/","native_build":true,"dependencies":[]},{"name":"location","path":"/home/admin1/.pub-cache/hosted/pub.dev/location-5.0.3/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/home/admin1/.pub-cache/hosted/pub.dev/package_info_plus-4.2.0/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/admin1/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"flutter_keyboard_visibility","path":"/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility-5.4.1/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/home/admin1/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"geolocator_android","path":"/home/admin1/.pub-cache/hosted/pub.dev/geolocator_android-4.4.0/","native_build":true,"dependencies":[]},{"name":"location","path":"/home/admin1/.pub-cache/hosted/pub.dev/location-5.0.3/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/home/admin1/.pub-cache/hosted/pub.dev/package_info_plus-4.2.0/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/home/admin1/.pub-cache/hosted/pub.dev/path_provider_android-2.2.2/","native_build":true,"dependencies":[]}],"macos":[{"name":"flutter_keyboard_visibility_macos","path":"/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_macos-1.0.0/","native_build":false,"dependencies":[]},{"name":"geolocator_apple","path":"/home/admin1/.pub-cache/hosted/pub.dev/geolocator_apple-2.3.3/","native_build":true,"dependencies":[]},{"name":"location","path":"/home/admin1/.pub-cache/hosted/pub.dev/location-5.0.3/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/home/admin1/.pub-cache/hosted/pub.dev/package_info_plus-4.2.0/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/admin1/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"flutter_keyboard_visibility_linux","path":"/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_linux-1.0.0/","native_build":false,"dependencies":[]},{"name":"package_info_plus","path":"/home/admin1/.pub-cache/hosted/pub.dev/package_info_plus-4.2.0/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/home/admin1/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"flutter_keyboard_visibility_windows","path":"/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_windows-1.0.0/","native_build":false,"dependencies":[]},{"name":"geolocator_windows","path":"/home/admin1/.pub-cache/hosted/pub.dev/geolocator_windows-0.2.2/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/home/admin1/.pub-cache/hosted/pub.dev/package_info_plus-4.2.0/","native_build":false,"dependencies":[]},{"name":"path_provider_windows","path":"/home/admin1/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]}],"web":[{"name":"flutter_keyboard_visibility_web","path":"/home/admin1/.pub-cache/hosted/pub.dev/flutter_keyboard_visibility_web-2.0.0/","dependencies":[]},{"name":"fluttertoast","path":"/home/admin1/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","dependencies":[]},{"name":"geolocator_web","path":"/home/admin1/.pub-cache/hosted/pub.dev/geolocator_web-2.2.0/","dependencies":[]},{"name":"location_web","path":"/home/admin1/.pub-cache/hosted/pub.dev/location_web-4.2.0/","dependencies":[]},{"name":"package_info_plus","path":"/home/admin1/.pub-cache/hosted/pub.dev/package_info_plus-4.2.0/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_keyboard_visibility","dependencies":["flutter_keyboard_visibility_linux","flutter_keyboard_visibility_macos","flutter_keyboard_visibility_web","flutter_keyboard_visibility_windows"]},{"name":"flutter_keyboard_visibility_linux","dependencies":[]},{"name":"flutter_keyboard_visibility_macos","dependencies":[]},{"name":"flutter_keyboard_visibility_web","dependencies":[]},{"name":"flutter_keyboard_visibility_windows","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"geolocator","dependencies":["geolocator_android","geolocator_apple","geolocator_web","geolocator_windows"]},{"name":"geolocator_android","dependencies":[]},{"name":"geolocator_apple","dependencies":[]},{"name":"geolocator_web","dependencies":[]},{"name":"geolocator_windows","dependencies":[]},{"name":"location","dependencies":["location_web"]},{"name":"location_web","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2024-01-12 10:07:41.013264","version":"3.10.0"} \ No newline at end of file diff --git a/flutter/digit-ui-components/digit_components/.gitignore b/flutter/digit-ui-components/digit_components/.gitignore new file mode 100644 index 00000000000..96486fd9302 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/flutter/digit-ui-components/digit_components/.metadata b/flutter/digit-ui-components/digit_components/.metadata new file mode 100644 index 00000000000..3063e24e1c4 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 18a827f3933c19f51862dde3fa472197683249d6 + channel: stable + +project_type: package diff --git a/flutter/digit-ui-components/digit_components/CHANGELOG.md b/flutter/digit-ui-components/digit_components/CHANGELOG.md new file mode 100644 index 00000000000..41cc7d8192e --- /dev/null +++ b/flutter/digit-ui-components/digit_components/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/flutter/digit-ui-components/digit_components/LICENSE b/flutter/digit-ui-components/digit_components/LICENSE new file mode 100644 index 00000000000..eb8349f732b --- /dev/null +++ b/flutter/digit-ui-components/digit_components/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 eGovernments Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/flutter/digit-ui-components/digit_components/README.md b/flutter/digit-ui-components/digit_components/README.md new file mode 100644 index 00000000000..02fe8ecabcb --- /dev/null +++ b/flutter/digit-ui-components/digit_components/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/flutter/digit-ui-components/digit_components/analysis_options.yaml b/flutter/digit-ui-components/digit_components/analysis_options.yaml new file mode 100644 index 00000000000..9d8e06d93ad --- /dev/null +++ b/flutter/digit-ui-components/digit_components/analysis_options.yaml @@ -0,0 +1,6 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: + exclude: + - "**/*.g.dart" + - "**/*.freezed.dart" \ No newline at end of file diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/LICENSE.txt b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/LICENSE.txt new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Black.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Black.ttf new file mode 100644 index 00000000000..0112e7da626 Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Black.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-BlackItalic.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-BlackItalic.ttf new file mode 100644 index 00000000000..b2c6aca57bc Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-BlackItalic.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Bold.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Bold.ttf new file mode 100644 index 00000000000..43da14d84ec Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Bold.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-BoldItalic.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-BoldItalic.ttf new file mode 100644 index 00000000000..bcfdab4311f Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-BoldItalic.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Italic.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Italic.ttf new file mode 100644 index 00000000000..1b5eaa361c7 Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Italic.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Light.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Light.ttf new file mode 100644 index 00000000000..e7307e72c5e Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Light.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-LightItalic.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-LightItalic.ttf new file mode 100644 index 00000000000..2d277afb231 Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-LightItalic.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Medium.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Medium.ttf new file mode 100644 index 00000000000..ac0f908b9c9 Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Medium.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-MediumItalic.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-MediumItalic.ttf new file mode 100644 index 00000000000..fc36a4785c5 Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-MediumItalic.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Regular.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Regular.ttf new file mode 100644 index 00000000000..ddf4bfacb39 Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Regular.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Thin.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Thin.ttf new file mode 100644 index 00000000000..2e0dee6a833 Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/Roboto-Thin.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/RobotoCondensed-Regular.ttf b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/RobotoCondensed-Regular.ttf new file mode 100644 index 00000000000..17e8ea57b11 Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/fonts/Roboto/RobotoCondensed-Regular.ttf differ diff --git a/flutter/digit-ui-components/digit_components/assets/images/powered_by_digit.png b/flutter/digit-ui-components/digit_components/assets/images/powered_by_digit.png new file mode 100644 index 00000000000..f83334fd616 Binary files /dev/null and b/flutter/digit-ui-components/digit_components/assets/images/powered_by_digit.png differ diff --git a/flutter/digit-ui-components/digit_components/coverage/lcov.info b/flutter/digit-ui-components/digit_components/coverage/lcov.info new file mode 100644 index 00000000000..f8003b52796 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/coverage/lcov.info @@ -0,0 +1,1833 @@ +SF:lib/widgets/atoms/digit_checkbox.dart +DA:47,1 +DA:55,1 +DA:57,1 +DA:58,1 +DA:64,1 +DA:66,1 +DA:67,3 +DA:70,1 +DA:72,1 +DA:73,1 +DA:74,2 +DA:75,1 +DA:76,1 +DA:77,1 +DA:78,1 +DA:79,2 +DA:81,1 +DA:82,2 +DA:83,2 +DA:85,2 +DA:86,4 +DA:89,1 +DA:91,1 +DA:92,1 +DA:95,1 +DA:96,1 +DA:97,2 +DA:98,2 +DA:102,1 +DA:103,2 +DA:104,5 +DA:105,4 +LF:32 +LH:32 +end_of_record +SF:lib/widgets/atoms/digit_checkbox_icon.dart +DA:20,5 +DA:26,3 +DA:28,3 +DA:32,3 +DA:33,3 +DA:34,3 +DA:35,3 +DA:38,3 +DA:39,3 +DA:40,9 +DA:46,3 +DA:47,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:57,0 +DA:58,0 +DA:61,0 +DA:65,3 +DA:66,3 +DA:69,3 +DA:70,3 +DA:71,9 +DA:76,3 +DA:77,3 +DA:80,9 +LF:26 +LH:19 +end_of_record +SF:lib/constants/DigitCheckboxConstants.dart +DA:11,3 +DA:12,4 +DA:14,3 +DA:15,2 +DA:17,0 +DA:18,0 +DA:20,3 +DA:21,2 +LF:8 +LH:6 +end_of_record +SF:lib/theme/colors.dart +DA:4,62 +DA:6,15 +DA:7,13 +DA:8,13 +DA:9,0 +DA:10,15 +DA:11,15 +DA:12,14 +DA:13,13 +DA:14,13 +DA:15,13 +DA:16,13 +DA:17,13 +DA:18,13 +DA:19,0 +DA:20,13 +DA:21,13 +DA:22,13 +DA:23,13 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,13 +LF:26 +LH:18 +end_of_record +SF:lib/digit_components.dart +DA:10,1 +DA:12,16 +DA:14,1 +LF:3 +LH:3 +end_of_record +SF:lib/theme/digit_theme.dart +DA:12,13 +DA:14,13 +DA:16,26 +DA:23,26 +DA:24,26 +DA:27,16 +DA:29,13 +DA:32,13 +DA:33,13 +DA:34,26 +DA:35,26 +DA:37,13 +DA:38,13 +DA:39,13 +DA:40,13 +DA:41,13 +DA:42,13 +DA:46,26 +DA:48,26 +DA:49,26 +DA:50,26 +DA:51,26 +DA:52,26 +DA:53,26 +DA:54,26 +DA:55,26 +DA:56,26 +DA:57,26 +DA:58,26 +DA:59,26 +DA:60,26 +DA:61,26 +DA:62,26 +DA:63,26 +DA:64,26 +DA:65,26 +DA:68,13 +DA:73,0 +DA:75,0 +DA:79,0 +DA:81,13 +DA:85,26 +DA:86,13 +DA:87,13 +DA:88,13 +DA:89,26 +DA:90,26 +DA:91,39 +DA:94,26 +DA:99,26 +DA:100,13 +DA:101,26 +DA:102,39 +DA:103,13 +DA:107,26 +DA:108,13 +DA:109,13 +DA:110,13 +DA:112,26 +DA:116,13 +DA:126,26 +DA:127,13 +DA:133,13 +DA:134,26 +DA:137,13 +DA:143,13 +DA:144,26 +DA:148,13 +DA:154,39 +DA:160,13 +DA:166,13 +DA:167,26 +DA:170,13 +DA:176,39 +DA:180,26 +DA:181,26 +DA:182,26 +DA:193,0 +DA:194,0 +DA:198,0 +DA:199,0 +LF:81 +LH:74 +end_of_record +SF:lib/theme/typography.dart +DA:9,13 +DA:19,13 +DA:20,39 +DA:21,13 +DA:22,39 +DA:23,13 +DA:24,39 +DA:32,0 +DA:33,39 +DA:34,0 +DA:35,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:43,39 +DA:44,0 +DA:45,0 +DA:48,3 +DA:49,3 +DA:53,13 +DA:58,13 +DA:65,13 +DA:67,13 +DA:68,26 +DA:73,26 +DA:77,26 +DA:81,26 +DA:86,26 +DA:91,26 +DA:96,26 +DA:101,26 +DA:105,26 +DA:110,26 +LF:33 +LH:25 +end_of_record +SF:lib/widgets/powered_by_digit.dart +DA:10,1 +DA:17,1 +DA:18,1 +DA:22,1 +DA:23,1 +DA:24,1 +DA:25,2 +DA:26,1 +DA:28,2 +DA:30,1 +DA:33,2 +DA:34,0 +DA:37,3 +LF:13 +LH:12 +end_of_record +SF:lib/widgets/scrollable_content.dart +DA:13,0 +DA:25,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:45,0 +DA:47,0 +LF:19 +LH:0 +end_of_record +SF:lib/utils/validators/validator.dart +DA:2,2 +DA:3,4 +DA:4,2 +DA:12,2 +DA:13,2 +DA:14,2 +DA:15,0 +DA:16,0 +DA:19,2 +DA:20,0 +DA:21,0 +DA:24,2 +DA:25,4 +DA:26,1 +DA:29,1 +DA:30,0 +DA:31,0 +DA:34,1 +DA:35,0 +DA:36,0 +DA:39,1 +DA:40,0 +DA:41,0 +DA:47,1 +DA:48,0 +DA:49,0 +DA:53,1 +DA:55,1 +DA:57,2 +DA:58,1 +DA:62,1 +DA:64,1 +DA:66,2 +DA:67,0 +DA:97,3 +LF:35 +LH:22 +end_of_record +SF:lib/widgets/atoms/digit_text_form_input.dart +DA:26,1 +DA:45,1 +DA:66,1 +DA:67,1 +DA:71,1 +DA:74,1 +LF:6 +LH:6 +end_of_record +SF:lib/widgets/atoms/digit_time_form_input.dart +DA:24,1 +DA:42,1 +DA:62,1 +DA:63,1 +DA:70,1 +DA:73,1 +DA:74,1 +DA:75,1 +DA:76,1 +DA:77,1 +DA:78,2 +DA:85,2 +DA:86,4 +DA:91,1 +DA:94,1 +LF:15 +LH:15 +end_of_record +SF:lib/constants/AppView.dart +DA:9,11 +DA:10,11 +DA:13,0 +DA:14,0 +DA:17,0 +DA:18,0 +LF:6 +LH:2 +end_of_record +SF:lib/constants/BaseFormInputConstants.dart +DA:20,24 +DA:21,8 +DA:22,8 +DA:26,24 +DA:27,8 +DA:28,8 +LF:6 +LH:6 +end_of_record +SF:lib/utils/time_utils.dart +DA:3,1 +DA:5,1 +DA:6,1 +DA:7,2 +DA:9,3 +DA:10,2 +DA:12,1 +LF:7 +LH:7 +end_of_record +SF:lib/widgets/atoms/digit_base_form_input.dart +DA:98,8 +DA:129,8 +DA:131,0 +DA:132,0 +DA:143,7 +DA:144,14 +DA:146,4 +DA:147,12 +DA:148,4 +DA:152,2 +DA:153,4 +DA:158,8 +DA:160,8 +DA:162,16 +DA:164,24 +DA:166,16 +DA:167,10 +DA:171,8 +DA:173,8 +DA:174,24 +DA:175,16 +DA:178,2 +DA:179,2 +DA:181,4 +DA:184,4 +DA:185,2 +DA:186,2 +DA:192,1 +DA:193,2 +DA:194,2 +DA:198,0 +DA:200,0 +DA:203,0 +DA:205,0 +DA:208,8 +DA:210,1 +DA:211,2 +DA:212,2 +DA:213,1 +DA:219,16 +DA:220,8 +DA:221,3 +DA:226,32 +DA:230,8 +DA:232,8 +DA:234,8 +DA:235,8 +DA:236,8 +DA:237,16 +DA:238,3 +DA:239,6 +DA:240,12 +DA:241,3 +DA:242,3 +DA:245,24 +DA:246,2 +DA:247,4 +DA:248,4 +DA:249,4 +DA:260,8 +DA:261,8 +DA:262,8 +DA:263,16 +DA:264,16 +DA:265,16 +DA:267,16 +DA:268,16 +DA:269,16 +DA:270,16 +DA:272,16 +DA:273,8 +DA:275,8 +DA:276,8 +DA:277,0 +DA:278,0 +DA:282,8 +DA:283,24 +DA:287,24 +DA:295,16 +DA:297,16 +DA:298,7 +DA:299,8 +DA:300,8 +DA:301,8 +DA:302,8 +DA:303,2 +DA:304,8 +DA:309,8 +DA:310,8 +DA:311,16 +DA:317,16 +DA:325,16 +DA:326,2 +DA:327,6 +DA:328,2 +DA:335,2 +DA:336,2 +DA:337,2 +DA:338,2 +DA:339,4 +DA:340,1 +DA:341,2 +DA:349,2 +DA:350,4 +DA:352,4 +DA:353,1 +DA:354,2 +DA:358,14 +DA:359,5 +DA:360,15 +DA:361,5 +DA:365,5 +DA:366,10 +DA:368,10 +DA:374,16 +DA:375,4 +DA:376,8 +DA:377,16 +DA:378,2 +DA:379,6 +DA:380,2 +DA:387,2 +DA:388,2 +DA:389,2 +DA:390,2 +DA:391,4 +DA:392,1 +DA:393,2 +DA:401,2 +DA:402,4 +DA:404,4 +DA:405,1 +DA:406,2 +DA:412,6 +DA:413,12 +DA:414,6 +DA:416,13 +DA:422,32 +DA:423,8 +DA:425,8 +DA:426,24 +DA:427,6 +DA:428,8 +DA:429,2 +DA:430,2 +DA:432,2 +DA:435,2 +DA:436,2 +DA:438,8 +DA:439,2 +DA:440,2 +DA:445,4 +DA:446,8 +DA:448,16 +DA:449,4 +DA:450,4 +DA:453,32 +DA:454,8 +DA:455,24 +DA:456,4 +DA:457,20 +LF:161 +LH:153 +end_of_record +SF:lib/widgets/atoms/digit_location_form_input.dart +DA:22,1 +DA:40,1 +DA:60,1 +DA:61,1 +DA:68,0 +DA:71,0 +DA:74,1 +DA:77,1 +LF:8 +LH:6 +end_of_record +SF:lib/blocs/LocationBloc.dart +DA:6,0 +DA:7,0 +DA:8,0 +DA:10,0 +DA:13,0 +DA:14,0 +DA:16,0 +DA:21,0 +LF:8 +LH:0 +end_of_record +SF:lib/widgets/atoms/digit_text_area_form_input.dart +DA:24,1 +DA:44,1 +DA:66,1 +DA:67,1 +DA:71,1 +DA:74,1 +LF:6 +LH:6 +end_of_record +SF:lib/widgets/atoms/digit_button.dart +DA:55,1 +DA:66,1 +DA:68,1 +DA:69,1 +DA:75,1 +DA:77,2 +DA:78,1 +DA:79,1 +DA:80,0 +DA:81,0 +DA:82,0 +DA:85,0 +DA:86,0 +DA:87,0 +DA:90,1 +DA:95,1 +DA:96,3 +DA:97,3 +DA:98,1 +DA:99,4 +DA:100,1 +DA:101,1 +DA:102,1 +DA:103,1 +DA:104,2 +DA:105,1 +DA:106,3 +DA:107,1 +DA:108,1 +DA:109,1 +DA:110,0 +DA:111,1 +DA:115,1 +DA:117,1 +DA:118,2 +DA:119,1 +DA:120,1 +DA:121,1 +DA:123,3 +DA:124,2 +DA:125,1 +DA:126,1 +DA:129,1 +DA:134,1 +DA:135,4 +DA:136,1 +DA:137,1 +DA:138,1 +DA:139,1 +DA:146,1 +DA:147,1 +DA:148,1 +DA:149,2 +DA:150,1 +DA:152,1 +DA:153,2 +DA:154,0 +DA:155,0 +DA:156,0 +DA:157,0 +DA:158,0 +DA:159,0 +DA:160,0 +DA:161,0 +DA:164,3 +DA:165,4 +DA:166,1 +DA:167,3 +DA:168,1 +DA:169,2 +DA:170,1 +DA:171,1 +DA:172,3 +DA:176,2 +DA:177,0 +DA:178,0 +DA:179,0 +DA:180,0 +DA:181,0 +DA:182,0 +DA:183,0 +DA:184,0 +LF:82 +LH:59 +end_of_record +SF:lib/constants/DigitButtonConstants.dart +DA:11,3 +DA:12,3 +DA:13,3 +DA:14,3 +LF:4 +LH:4 +end_of_record +SF:lib/widgets/atoms/digit_search_form_input.dart +DA:25,1 +DA:43,1 +DA:63,1 +DA:64,1 +DA:70,1 +DA:73,6 +DA:75,1 +DA:77,1 +LF:8 +LH:8 +end_of_record +SF:lib/widgets/atoms/digit_password_form_input.dart +DA:23,1 +DA:42,1 +DA:63,1 +DA:64,1 +DA:69,1 +DA:72,1 +DA:75,1 +DA:78,1 +DA:81,1 +DA:85,1 +DA:87,1 +DA:89,1 +DA:92,1 +LF:13 +LH:13 +end_of_record +SF:lib/models/DropdownModels.dart +DA:16,3 +DA:38,3 +DA:62,2 +LF:3 +LH:3 +end_of_record +SF:lib/widgets/atoms/digit_dropdown_input.dart +DA:37,1 +DA:48,1 +DA:50,1 +DA:51,1 +DA:70,1 +DA:72,1 +DA:73,3 +DA:74,4 +DA:75,4 +DA:76,6 +DA:77,2 +DA:79,2 +DA:80,1 +DA:83,4 +DA:84,1 +DA:89,1 +DA:91,3 +DA:92,2 +DA:96,2 +DA:98,1 +DA:101,1 +DA:102,2 +DA:103,0 +DA:107,1 +DA:109,2 +DA:113,4 +DA:118,1 +DA:119,1 +DA:120,1 +DA:121,1 +DA:122,1 +DA:123,1 +DA:124,1 +DA:125,1 +DA:126,3 +DA:128,1 +DA:129,1 +DA:130,3 +DA:131,1 +DA:132,2 +DA:135,1 +DA:136,2 +DA:137,1 +DA:141,1 +DA:142,1 +DA:143,1 +DA:146,1 +DA:147,1 +DA:148,1 +DA:151,1 +DA:155,1 +DA:156,1 +DA:157,2 +DA:159,1 +DA:167,1 +DA:168,2 +DA:169,2 +DA:170,1 +DA:171,1 +DA:172,1 +DA:173,3 +DA:174,1 +DA:175,0 +DA:176,0 +DA:177,0 +DA:178,0 +DA:179,1 +DA:181,2 +DA:182,2 +DA:183,1 +DA:189,1 +DA:190,2 +DA:191,2 +DA:192,2 +DA:193,4 +DA:198,1 +DA:200,2 +DA:204,0 +DA:206,1 +DA:208,1 +DA:209,3 +DA:210,1 +DA:213,2 +DA:214,2 +DA:218,1 +DA:219,3 +DA:220,3 +DA:221,1 +DA:222,1 +DA:223,1 +DA:224,1 +DA:226,4 +DA:227,1 +DA:228,5 +DA:229,1 +DA:231,1 +DA:232,3 +DA:233,3 +DA:234,3 +DA:236,1 +DA:238,1 +DA:239,1 +DA:240,3 +DA:241,1 +DA:242,4 +DA:243,1 +DA:246,1 +DA:261,1 +DA:262,2 +DA:263,1 +DA:264,1 +DA:266,1 +DA:268,1 +DA:269,0 +DA:271,0 +DA:273,1 +DA:274,1 +DA:276,1 +DA:281,1 +DA:282,1 +DA:285,2 +DA:286,5 +DA:287,3 +DA:288,1 +DA:289,1 +DA:291,1 +DA:292,1 +DA:293,1 +DA:294,1 +DA:295,1 +DA:296,0 +DA:297,0 +DA:298,0 +DA:301,0 +DA:302,0 +DA:303,0 +DA:304,0 +DA:306,1 +DA:307,1 +DA:308,1 +DA:309,3 +DA:310,0 +DA:313,3 +DA:314,0 +DA:318,1 +DA:320,3 +DA:321,2 +DA:324,1 +DA:325,1 +DA:326,3 +DA:328,0 +DA:331,0 +DA:335,0 +DA:336,0 +DA:339,3 +DA:341,0 +DA:344,1 +DA:346,1 +DA:347,1 +DA:348,1 +DA:349,2 +DA:350,1 +DA:351,2 +DA:353,1 +DA:355,2 +DA:356,1 +DA:359,2 +DA:360,2 +DA:361,2 +DA:362,1 +DA:363,3 +DA:364,1 +DA:366,1 +DA:368,3 +DA:371,2 +DA:372,1 +DA:373,2 +DA:374,2 +DA:375,2 +DA:376,1 +DA:377,1 +DA:389,1 +DA:390,1 +DA:391,1 +DA:392,1 +DA:394,3 +DA:401,1 +DA:402,1 +DA:405,1 +DA:409,1 +DA:410,1 +DA:411,5 +DA:413,2 +DA:416,1 +DA:417,1 +DA:419,1 +DA:420,1 +DA:421,4 +DA:422,1 +DA:423,1 +DA:430,6 +DA:432,2 +DA:433,1 +DA:434,1 +DA:435,1 +DA:436,1 +DA:437,1 +DA:438,1 +DA:439,1 +DA:440,1 +DA:441,0 +DA:442,0 +DA:443,0 +DA:446,1 +DA:447,2 +DA:448,3 +DA:449,1 +DA:451,4 +DA:452,1 +DA:454,1 +DA:455,3 +DA:456,1 +DA:457,1 +DA:458,3 +DA:459,0 +DA:462,3 +DA:463,0 +DA:464,1 +DA:467,1 +DA:469,1 +DA:470,1 +DA:476,1 +DA:477,1 +DA:478,5 +DA:479,1 +DA:483,1 +DA:484,0 +DA:489,0 +DA:490,0 +DA:492,0 +DA:493,0 +DA:494,0 +DA:504,1 +DA:506,1 +DA:507,3 +DA:524,1 +DA:525,0 +DA:526,0 +DA:527,0 +DA:529,0 +DA:539,1 +DA:540,1 +DA:541,2 +DA:542,2 +DA:543,2 +DA:544,1 +DA:547,2 +DA:548,2 +DA:551,2 +DA:554,2 +DA:556,4 +DA:557,3 +DA:558,2 +DA:560,3 +DA:561,0 +DA:562,0 +DA:565,3 +DA:566,3 +DA:567,2 +DA:568,4 +LF:270 +LH:231 +end_of_record +SF:lib/widgets/atoms/digit_multiselect_dropdown.dart +DA:51,1 +DA:69,1 +DA:71,1 +DA:73,1 +DA:94,1 +DA:96,1 +DA:97,3 +DA:98,1 +DA:100,4 +DA:101,4 +DA:104,1 +DA:105,1 +DA:106,1 +DA:107,3 +DA:108,3 +DA:112,1 +DA:113,2 +DA:114,6 +DA:115,0 +DA:116,2 +DA:119,3 +DA:120,4 +DA:121,3 +DA:122,3 +DA:123,3 +DA:129,1 +DA:130,3 +DA:131,2 +DA:132,4 +DA:136,4 +DA:137,2 +DA:140,1 +DA:141,2 +DA:142,4 +DA:146,1 +DA:147,6 +DA:152,0 +DA:154,0 +DA:155,0 +DA:156,0 +DA:157,0 +DA:158,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:167,0 +DA:168,0 +DA:173,0 +DA:175,0 +DA:176,0 +DA:177,0 +DA:180,0 +DA:181,0 +DA:185,0 +DA:189,1 +DA:190,2 +DA:192,1 +DA:193,1 +DA:195,6 +DA:197,1 +DA:200,1 +DA:202,4 +DA:203,1 +DA:204,1 +DA:205,1 +DA:206,1 +DA:207,1 +DA:210,1 +DA:211,1 +DA:212,1 +DA:213,1 +DA:214,1 +DA:215,1 +DA:216,2 +DA:217,1 +DA:226,1 +DA:227,1 +DA:228,1 +DA:229,1 +DA:230,2 +DA:231,4 +DA:234,1 +DA:235,1 +DA:237,2 +DA:249,1 +DA:250,1 +DA:253,1 +DA:260,1 +DA:261,2 +DA:265,1 +DA:269,3 +DA:272,1 +DA:273,1 +DA:276,1 +DA:277,1 +DA:278,1 +DA:279,0 +DA:282,1 +DA:283,1 +DA:290,1 +DA:292,3 +DA:293,0 +DA:294,0 +DA:296,0 +DA:297,0 +DA:299,3 +DA:300,2 +DA:301,3 +DA:303,2 +DA:304,2 +DA:307,1 +DA:311,0 +DA:315,0 +DA:316,0 +DA:317,0 +DA:322,1 +DA:323,2 +DA:327,1 +DA:328,2 +DA:329,0 +DA:331,2 +DA:336,1 +DA:338,1 +DA:340,1 +DA:342,2 +DA:343,2 +DA:344,2 +DA:346,2 +DA:347,1 +DA:348,1 +DA:349,1 +DA:350,1 +DA:352,1 +DA:353,1 +DA:357,1 +DA:358,1 +DA:363,1 +DA:366,1 +DA:367,1 +DA:368,1 +DA:370,1 +DA:371,3 +DA:372,1 +DA:374,1 +DA:387,1 +DA:392,1 +DA:393,1 +DA:394,2 +DA:396,1 +DA:397,1 +DA:401,1 +DA:402,1 +DA:403,1 +DA:404,1 +DA:405,7 +DA:406,2 +DA:407,1 +DA:408,1 +DA:409,1 +DA:421,1 +DA:427,3 +DA:429,1 +DA:430,1 +DA:431,2 +DA:433,1 +DA:435,1 +DA:436,1 +DA:437,2 +DA:438,1 +DA:440,1 +DA:442,1 +DA:444,1 +DA:446,1 +DA:447,1 +DA:449,5 +DA:450,1 +DA:454,2 +DA:455,2 +DA:456,6 +DA:457,3 +DA:458,1 +DA:459,1 +DA:461,1 +DA:468,1 +DA:476,1 +DA:482,1 +DA:483,1 +DA:484,1 +DA:485,1 +DA:486,1 +DA:487,1 +DA:488,1 +DA:489,1 +DA:491,1 +DA:493,1 +DA:496,1 +DA:497,1 +DA:500,1 +DA:502,1 +DA:503,1 +DA:504,1 +DA:505,2 +DA:506,0 +DA:507,0 +DA:510,0 +DA:511,0 +DA:513,2 +DA:514,0 +DA:517,1 +DA:518,1 +DA:520,5 +DA:522,1 +DA:523,1 +DA:528,1 +DA:529,0 +DA:530,0 +DA:531,0 +DA:532,0 +DA:534,0 +DA:535,0 +DA:542,1 +DA:543,1 +DA:546,3 +DA:547,1 +DA:549,1 +DA:552,1 +DA:554,0 +DA:555,0 +DA:557,0 +DA:558,0 +DA:561,2 +DA:562,1 +DA:564,2 +DA:565,2 +DA:569,1 +DA:570,4 +DA:571,5 +DA:573,4 +DA:576,1 +DA:578,1 +DA:579,4 +DA:591,1 +DA:592,3 +DA:593,2 +DA:595,0 +DA:596,0 +DA:598,0 +DA:600,2 +DA:604,1 +DA:605,1 +DA:606,3 +DA:607,3 +DA:608,1 +DA:609,5 +DA:610,1 +DA:611,1 +DA:613,3 +DA:617,1 +DA:618,1 +DA:619,1 +DA:620,2 +DA:621,1 +DA:622,1 +DA:623,1 +DA:624,1 +DA:625,1 +DA:627,1 +DA:632,1 +DA:633,2 +DA:641,1 +DA:642,1 +DA:644,2 +DA:646,0 +DA:647,0 +DA:648,0 +DA:650,0 +DA:651,0 +DA:653,0 +DA:655,0 +DA:661,1 +DA:663,4 +DA:666,5 +DA:667,2 +DA:668,2 +DA:669,5 +DA:674,5 +DA:675,2 +DA:676,2 +DA:677,5 +DA:679,4 +DA:682,5 +DA:683,0 +DA:684,0 +DA:686,0 +DA:706,3 +DA:710,2 +DA:713,1 +DA:715,1 +DA:716,1 +DA:721,1 +DA:722,3 +DA:723,1 +DA:728,0 +DA:729,0 +DA:730,0 +DA:731,0 +DA:736,1 +DA:737,1 +DA:738,0 +DA:741,3 +DA:742,3 +DA:743,1 +DA:748,0 +DA:750,0 +DA:751,0 +DA:754,0 +DA:755,0 +DA:760,1 +DA:761,3 +DA:762,3 +DA:763,1 +DA:767,0 +DA:770,3 +DA:773,0 +DA:777,0 +DA:778,0 +DA:779,0 +DA:780,0 +DA:785,0 +DA:786,0 +DA:787,0 +DA:788,0 +LF:332 +LH:255 +end_of_record +SF:lib/models/chipModel.dart +DA:22,3 +LF:1 +LH:1 +end_of_record +SF:lib/widgets/helper_widget/selection_chip.dart +DA:14,2 +DA:20,2 +DA:22,2 +DA:24,2 +DA:25,4 +DA:26,4 +DA:27,6 +DA:28,6 +DA:29,2 +DA:30,2 +DA:32,2 +DA:34,2 +DA:36,4 +DA:38,2 +DA:39,12 +DA:40,0 +LF:16 +LH:15 +end_of_record +SF:lib/models/RadioButtonModel.dart +DA:5,1 +LF:1 +LH:1 +end_of_record +SF:lib/widgets/atoms/digit_radio_list.dart +DA:52,1 +DA:61,1 +DA:64,1 +DA:65,1 +DA:74,1 +DA:76,1 +DA:78,6 +DA:82,1 +DA:84,4 +DA:86,0 +DA:88,0 +DA:92,1 +DA:94,1 +DA:99,1 +DA:100,3 +DA:101,1 +DA:102,3 +DA:103,1 +DA:104,2 +DA:105,1 +DA:107,1 +DA:108,1 +DA:109,0 +DA:110,0 +DA:111,0 +DA:114,0 +DA:115,0 +DA:116,0 +DA:119,1 +DA:120,2 +DA:122,1 +DA:123,2 +DA:125,7 +DA:127,5 +DA:129,1 +DA:131,1 +DA:132,1 +DA:134,2 +DA:135,2 +DA:136,1 +DA:138,1 +DA:139,2 +DA:140,1 +DA:141,4 +DA:142,2 +DA:143,1 +DA:144,1 +DA:147,1 +DA:149,4 +DA:150,1 +DA:151,1 +DA:153,2 +DA:154,0 +DA:155,1 +DA:163,1 +DA:165,1 +DA:166,1 +DA:167,5 +DA:168,4 +DA:180,1 +LF:60 +LH:51 +end_of_record +SF:lib/widgets/atoms/digit_numeric_form_input.dart +DA:29,1 +DA:53,1 +DA:80,1 +DA:81,1 +DA:86,1 +DA:88,2 +DA:90,4 +DA:91,6 +DA:92,7 +DA:98,1 +DA:100,2 +DA:102,4 +DA:103,6 +DA:104,7 +DA:109,1 +DA:112,1 +LF:16 +LH:16 +end_of_record +SF:lib/models/TreeModel.dart +DA:6,1 +LF:1 +LH:1 +end_of_record +SF:lib/widgets/atoms/digit_tree_select_dropdown.dart +DA:45,1 +DA:60,1 +DA:62,1 +DA:64,1 +DA:85,1 +DA:87,1 +DA:88,3 +DA:89,1 +DA:91,4 +DA:92,4 +DA:95,1 +DA:96,1 +DA:97,1 +DA:98,3 +DA:99,3 +DA:103,1 +DA:104,2 +DA:105,6 +DA:106,0 +DA:107,2 +DA:110,3 +DA:111,4 +DA:112,3 +DA:113,3 +DA:114,3 +DA:120,1 +DA:121,3 +DA:122,2 +DA:123,4 +DA:127,4 +DA:128,2 +DA:131,1 +DA:132,2 +DA:133,4 +DA:137,1 +DA:138,6 +DA:143,0 +DA:145,0 +DA:146,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:153,0 +DA:154,0 +DA:155,0 +DA:158,0 +DA:159,0 +DA:164,0 +DA:166,0 +DA:167,0 +DA:168,0 +DA:171,0 +DA:172,0 +DA:175,0 +DA:179,1 +DA:180,2 +DA:182,1 +DA:183,1 +DA:185,6 +DA:187,1 +DA:190,1 +DA:194,4 +DA:197,1 +DA:198,1 +DA:199,1 +DA:200,1 +DA:201,1 +DA:204,1 +DA:205,1 +DA:209,1 +DA:210,2 +DA:211,1 +DA:220,1 +DA:221,1 +DA:222,1 +DA:223,1 +DA:224,2 +DA:225,3 +DA:227,4 +DA:228,0 +DA:231,1 +DA:232,1 +DA:234,2 +DA:246,1 +DA:247,3 +DA:248,1 +DA:250,1 +DA:257,1 +DA:258,1 +DA:262,1 +DA:263,1 +DA:264,3 +DA:265,3 +DA:266,1 +DA:267,5 +DA:268,1 +DA:269,1 +DA:271,3 +DA:275,1 +DA:276,1 +DA:277,1 +DA:278,2 +DA:279,1 +DA:280,1 +DA:281,1 +DA:282,1 +DA:283,1 +DA:285,1 +DA:290,1 +DA:291,2 +DA:299,1 +DA:300,1 +DA:303,0 +DA:304,0 +DA:305,0 +DA:307,0 +DA:308,0 +DA:311,0 +DA:313,0 +DA:319,3 +DA:322,1 +DA:323,1 +DA:326,1 +DA:327,1 +DA:328,1 +DA:331,1 +DA:332,1 +DA:339,1 +DA:341,3 +DA:342,0 +DA:343,0 +DA:345,0 +DA:346,0 +DA:348,3 +DA:349,2 +DA:350,3 +DA:352,2 +DA:353,2 +DA:356,1 +DA:360,1 +DA:361,2 +DA:365,1 +DA:366,2 +DA:367,0 +DA:369,2 +DA:374,1 +DA:376,1 +DA:378,1 +DA:380,2 +DA:381,2 +DA:382,2 +DA:384,2 +DA:385,1 +DA:386,1 +DA:387,1 +DA:388,1 +DA:390,1 +DA:391,1 +DA:396,1 +DA:397,1 +DA:402,1 +DA:407,1 +DA:409,1 +DA:410,1 +DA:411,1 +DA:413,1 +DA:414,1 +DA:428,1 +DA:430,1 +DA:431,1 +DA:432,2 +DA:434,1 +DA:435,1 +DA:436,2 +DA:439,1 +DA:440,1 +DA:441,1 +DA:443,5 +DA:444,2 +DA:445,1 +DA:446,1 +DA:447,1 +DA:459,1 +DA:466,1 +DA:472,1 +DA:473,2 +DA:474,1 +DA:475,3 +DA:476,2 +DA:477,1 +DA:478,1 +DA:480,2 +DA:481,2 +DA:482,2 +DA:485,0 +DA:486,0 +DA:487,0 +DA:489,0 +DA:490,0 +DA:491,0 +DA:495,1 +DA:496,4 +DA:497,5 +DA:500,3 +DA:507,1 +DA:508,3 +DA:509,2 +DA:511,0 +DA:512,0 +DA:514,0 +DA:516,2 +DA:520,1 +DA:522,4 +DA:525,5 +DA:526,2 +DA:527,2 +DA:528,5 +DA:533,5 +DA:534,2 +DA:535,2 +DA:536,5 +DA:538,4 +DA:541,5 +DA:542,0 +DA:543,0 +DA:545,0 +DA:563,3 +DA:567,2 +DA:570,1 +DA:572,1 +DA:573,1 +DA:578,1 +DA:579,3 +DA:580,1 +DA:585,0 +DA:586,0 +DA:587,0 +DA:590,0 +DA:591,0 +DA:592,0 +DA:597,1 +DA:598,1 +DA:599,0 +DA:602,3 +DA:603,3 +DA:604,1 +DA:609,0 +DA:610,0 +DA:611,0 +DA:614,0 +DA:615,0 +DA:620,1 +DA:621,3 +DA:622,3 +DA:623,1 +DA:627,0 +DA:630,3 +DA:633,0 +DA:637,0 +DA:638,0 +DA:639,0 +DA:640,0 +DA:645,0 +DA:646,0 +DA:647,0 +DA:648,0 +LF:266 +LH:200 +end_of_record +SF:lib/widgets/helper_widget/tree_node_widget.dart +DA:17,1 +DA:27,1 +DA:29,1 +DA:30,1 +DA:38,1 +DA:39,2 +DA:40,7 +DA:43,0 +DA:47,1 +DA:48,2 +DA:49,0 +DA:50,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:60,0 +DA:61,0 +DA:67,0 +DA:68,0 +DA:69,0 +DA:70,0 +DA:72,0 +DA:73,0 +DA:78,1 +DA:79,7 +DA:82,1 +DA:84,1 +DA:86,3 +DA:91,2 +DA:92,1 +DA:94,3 +DA:95,0 +DA:96,1 +DA:97,1 +DA:100,1 +DA:101,1 +DA:102,1 +DA:103,1 +DA:104,1 +DA:105,2 +DA:106,1 +DA:109,1 +DA:112,3 +DA:113,1 +DA:114,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:119,0 +DA:121,0 +DA:122,0 +DA:123,0 +DA:126,3 +DA:130,3 +DA:136,1 +DA:139,1 +DA:140,3 +DA:141,4 +DA:142,1 +DA:143,1 +DA:148,1 +DA:149,0 +DA:151,0 +DA:152,0 +DA:153,0 +DA:154,0 +DA:155,0 +DA:156,0 +DA:164,0 +DA:166,0 +DA:167,0 +DA:169,0 +DA:170,0 +DA:171,0 +DA:173,0 +DA:174,0 +DA:175,0 +DA:176,0 +DA:177,0 +DA:180,0 +DA:187,1 +DA:188,4 +DA:189,0 +DA:190,0 +DA:192,2 +DA:193,7 +DA:194,0 +DA:195,0 +DA:197,3 +DA:198,2 +DA:199,7 +DA:200,3 +DA:201,5 +DA:204,0 +DA:205,0 +DA:207,0 +DA:208,0 +DA:209,0 +LF:99 +LH:47 +end_of_record +SF:lib/widgets/atoms/digit_date_form_input.dart +DA:22,1 +DA:40,1 +DA:60,1 +DA:61,1 +DA:68,1 +DA:73,2 +DA:74,1 +DA:75,2 +DA:79,1 +DA:82,1 +LF:10 +LH:10 +end_of_record +SF:lib/blocs/DateSelection.dart +DA:5,1 +DA:10,1 +DA:12,1 +DA:13,1 +DA:14,1 +DA:18,0 +DA:19,3 +DA:20,1 +LF:8 +LH:7 +end_of_record diff --git a/flutter/digit-ui-components/digit_components/lib/blocs/DateSelection.dart b/flutter/digit-ui-components/digit_components/lib/blocs/DateSelection.dart new file mode 100644 index 00000000000..fc4c343d9a7 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/blocs/DateSelection.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class DateSelectionBloc { + Future selectDate({ + required BuildContext context, + required TextEditingController controller, + DateFormat? dateFormat, /// Added parameter for custom date format + }) async { + DateTime? selectedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime(2101), + ); + if (selectedDate != null) { + String formattedDate = + dateFormat?.format(selectedDate.toLocal()) ?? + DateFormat('dd/MM/yyyy').format(selectedDate.toLocal()); + controller.text = formattedDate; + } + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/blocs/LocationBloc.dart b/flutter/digit-ui-components/digit_components/lib/blocs/LocationBloc.dart new file mode 100644 index 00000000000..7a36f265676 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/blocs/LocationBloc.dart @@ -0,0 +1,24 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; + +class LocationBloc { + Future getCurrentLocation(TextEditingController controller) async { + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + /// Request location permission + permission = await Geolocator.requestPermission(); + } + + if (permission == LocationPermission.whileInUse || + permission == LocationPermission.always) { + /// Get the current position + Position position = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.high, + ); + + /// Update the text controller with the current location + controller.text = "${position.latitude}, ${position.longitude}"; + } + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/blocs/TimeSelectionBloc.dart b/flutter/digit-ui-components/digit_components/lib/blocs/TimeSelectionBloc.dart new file mode 100644 index 00000000000..8df376f5798 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/blocs/TimeSelectionBloc.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import '../../utils/time_utils.dart'; + +class TimeSelectionBloc { + Future selectTime({ + required BuildContext context, + required TextEditingController controller, + }) async { + /// Show a time picker and update the controller's value + TimeOfDay? selectedTime = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: false), + child: child!, + ); + }, + ); + if (selectedTime != null) { + controller.text = formatTime(selectedTime); + } + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/constants/AppView.dart b/flutter/digit-ui-components/digit_components/lib/constants/AppView.dart new file mode 100644 index 00000000000..d5dd1ab93f6 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/constants/AppView.dart @@ -0,0 +1,20 @@ +class AppSizes { + /// Define breakpoints for different views + static const double mobileViewWidth = 400.0; + static const double tabletViewWidth = 700.0; +} + +class AppView { + /// Determine the current view based on the screen width + static bool isMobileView(double screenWidth) { + return screenWidth < AppSizes.mobileViewWidth; + } + + static bool isTabletView(double screenWidth) { + return screenWidth >= AppSizes.mobileViewWidth && screenWidth < AppSizes.tabletViewWidth; + } + + static bool isDesktopView(double screenWidth) { + return screenWidth >= AppSizes.tabletViewWidth; + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/constants/app_constants.dart b/flutter/digit-ui-components/digit_components/lib/constants/app_constants.dart new file mode 100644 index 00000000000..84d0ce06134 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/constants/app_constants.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +import '../theme/colors.dart'; +import '../theme/digit_theme.dart'; + +class Default{ + static const double height = 40; + static const double mobileInputWidth = 328; + static const double desktopInputWidth = 600; + static const double defaultChipRadius = 50; + static const EdgeInsets defaultChipPadding = EdgeInsets.only( + left: 12, + right: kPadding / 2, + ); +} + +class BaseConstants { + static const double suffixIconSize = 20.0; + static const double errorIconSize = 16.0; + static const double inputMaxHeight = 100.0; + static const double inputMinHeight = 40.0; + static const double mobileInputMaxWidth = 328.0; + static const double mobileInputMinWidth = 156.0; + static const double desktopInputMaxWidth = 600.0; + static const double desktopInputMinWidth = 200.0; + + static final OutlineInputBorder focusedBorder = OutlineInputBorder( + borderSide: BorderSide( + color: const DigitColors().burningOrange, width: 1.0), + borderRadius: BorderRadius.zero, + ); + + static final OutlineInputBorder disabledBorder = OutlineInputBorder( + borderSide: BorderSide( + color: const DigitColors().cloudGray, width: 1.0), + borderRadius: BorderRadius.zero, + ); + // Spacing + static const double defaultPadding = 12.0; +} + + +class DigitButtonConstants { + static const EdgeInsets defaultButtonPadding = EdgeInsets.all(8.0); + static const EdgeInsets defaultContentPadding = EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0); + static const double defaultIconSize = 20.0; + static final Color defaultDisabledColor = const DigitColors().cloudGray; + static final Color defaultPrimaryColor = const DigitColors().burningOrange; + static final Color defaultSecondaryColor = const DigitColors().woodsmokeBlack; + static final Color defaultTextColor = const DigitColors().white; + static const double defaultWidth = 1.0; + static const double defaultHoverWidth = 2.0; +} +class CheckboxConstants { + static const double iconSize = 16.0; + static const double containerSize = 24.0; + static const double borderWidth = 2.0; + static const EdgeInsets defaultPadding = EdgeInsets.only(left: 4.0); + + static Color uncheckedBorderColor({required bool isDisabled, required Color? customColor}) => + customColor ?? (isDisabled ? const DigitColors().cloudGray : const DigitColors().davyGray); + + static Color checkedBorderColor({required bool isDisabled, required Color? customColor}) => + customColor ?? (isDisabled ? const DigitColors().cloudGray : const DigitColors().burningOrange); + + static Color intermediateBorderColor({required bool isDisabled, required Color? customColor}) => + customColor ?? (isDisabled ? const DigitColors().cloudGray : const DigitColors().burningOrange); + + static Color iconColor({required bool isDisabled, required Color? customColor}) => + customColor ?? (isDisabled ? const DigitColors().cloudGray : const DigitColors().burningOrange); +} + +class DropdownConstants { + static const EdgeInsetsGeometry defaultPadding = + EdgeInsets.only(left: 10, top: 16, bottom: 16); + static const EdgeInsetsGeometry nestedItemPadding = + EdgeInsets.only(left: 10, top: 8, bottom: 8); + static const EdgeInsetsGeometry nestedItemHeaderPadding =EdgeInsets.all(10); + static const double defaultProfileSize = 32; + static const double defaultImageRadius = 72; + static const double textIconSize = 20; + static const EdgeInsets noItemAvailablePadding = EdgeInsets.all(8.0); + static const Duration animationDuration = Duration(milliseconds: 200); +} + +class RadioConstant { + static const EdgeInsets defaultPadding = EdgeInsets.all(8); + static const double radioWidth = 24.0; + static const double radioHeight = 24.0; +} \ No newline at end of file diff --git a/flutter/digit-ui-components/digit_components/lib/digit_components.dart b/flutter/digit-ui-components/digit_components/lib/digit_components.dart new file mode 100644 index 00000000000..8fbeca98d74 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/digit_components.dart @@ -0,0 +1,15 @@ +library digit_components; + +export 'theme/theme.dart'; +export 'widgets/widgets.dart'; + +class DigitUi { + static const DigitUi _instance = DigitUi._internal(); + static DigitUi get instance => _instance; + + const DigitUi._internal(); + + Future initThemeComponents() async { + /// can be used to add custom theme components + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/enum/app_enums.dart b/flutter/digit-ui-components/digit_components/lib/enum/app_enums.dart new file mode 100644 index 00000000000..bc4ff29dc04 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/enum/app_enums.dart @@ -0,0 +1,41 @@ +/// Enum representing the possible types of the button. +enum ButtonType { + primary, + secondary, + tertiary, + link, +} + + +/// Enum representing different types of single select dropdowns. +enum DropdownType { + defaultSelect, + nestedSelect, + profileSelect, +} + +/// Enum representing different types of multi select dropdowns. +enum SelectionType { + multiSelect, + nestedMultiSelect, +} + +/// Enum representing different types of multi select dropdowns. +enum TreeSelectionType { + singleSelect, + MultiSelect, +} + + + +/// Enum representing the possible states of the checkbox. +enum CheckboxState { + /// Represents an unchecked state. + unchecked, + + /// Represents an intermediate state (partially checked). + intermediate, + + /// Represents a checked state. + checked, +} \ No newline at end of file diff --git a/flutter/digit-ui-components/digit_components/lib/models/DropdownModels.dart b/flutter/digit-ui-components/digit_components/lib/models/DropdownModels.dart new file mode 100644 index 00000000000..637547e5851 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/models/DropdownModels.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +/// DropdownItem is just a wrapper for each child in the dropdown list.\n +/// It holds the value of the item. + +class DropdownItem { + final String name; + final String code; + final String? type; + final String? description; + final IconData? textIcon; + final NetworkImage? profileImage; + + const DropdownItem({ + required this.name, + required this.code, + this.type, + this.description, + this.textIcon, + this.profileImage, + }); +} diff --git a/flutter/digit-ui-components/digit_components/lib/models/RadioButtonModel.dart b/flutter/digit-ui-components/digit_components/lib/models/RadioButtonModel.dart new file mode 100644 index 00000000000..f6e24a6f7a1 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/models/RadioButtonModel.dart @@ -0,0 +1,9 @@ +class RadioButtonModel { + final String code; + final String name; + + RadioButtonModel({ + required this.code, + required this.name, + }); +} diff --git a/flutter/digit-ui-components/digit_components/lib/models/TreeModel.dart b/flutter/digit-ui-components/digit_components/lib/models/TreeModel.dart new file mode 100644 index 00000000000..f00b24691fb --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/models/TreeModel.dart @@ -0,0 +1,7 @@ +class TreeNode { + final String code; + final String name; + final List children; + + TreeNode(this.code, this.name, this.children); +} diff --git a/flutter/digit-ui-components/digit_components/lib/models/chipModel.dart b/flutter/digit-ui-components/digit_components/lib/models/chipModel.dart new file mode 100644 index 00000000000..b5424c5f52b --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/models/chipModel.dart @@ -0,0 +1,33 @@ +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; +import '../constants/app_constants.dart'; + +class ChipConfig { + final Icon? deleteIcon; + final Color deleteIconColor; + final Color labelColor; + final Color? backgroundColor; + final TextStyle? labelStyle; + final EdgeInsets padding; + final EdgeInsets labelPadding; + final double radius; + final double spacing; + final double runSpacing; + final Widget? separator; + final bool autoScroll; + + const ChipConfig({ + this.deleteIcon, + this.deleteIconColor = Colors.white, + this.backgroundColor, + this.padding = Default.defaultChipPadding, + this.radius = Default.defaultChipRadius, + this.spacing = kPadding, + this.runSpacing = kPadding, + this.separator, + this.labelColor = Colors.white, + this.labelStyle, + this.labelPadding = EdgeInsets.zero, + this.autoScroll = false, + }); +} diff --git a/flutter/digit-ui-components/digit_components/lib/models/toggleButtonModel.dart b/flutter/digit-ui-components/digit_components/lib/models/toggleButtonModel.dart new file mode 100644 index 00000000000..cf21d19b682 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/models/toggleButtonModel.dart @@ -0,0 +1,13 @@ +class ToggleButtonModel { + final String name; + final String key; + + /// Callback function when the button is selected + final void Function()? onSelected; + + ToggleButtonModel({ + required this.name, + required this.key, + this.onSelected, + }); +} diff --git a/flutter/digit-ui-components/digit_components/lib/theme/colors.dart b/flutter/digit-ui-components/digit_components/lib/theme/colors.dart new file mode 100644 index 00000000000..b48fb946482 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/theme/colors.dart @@ -0,0 +1,31 @@ +import 'dart:ui'; + +class DigitColors { + const DigitColors(); + + Color get burningOrange => const Color(0xFFF47738); + Color get regalBlue => const Color(0xFF0B4B66); + Color get woodsmokeBlack => const Color(0xFF0B0C0C); + Color get black => const Color(0xFF000000); + Color get davyGray => const Color(0xFF505A5F); + Color get cloudGray => const Color(0xFFB1B4B6); + Color get seaShellGray => const Color(0xFFEEEEEE); + Color get white => const Color(0xFFFFFFFF); + Color get alabasterWhite => const Color(0xFFFAFAFA); + Color get quillGray => const Color(0xFFD6D5D4); + Color get paleRose => const Color(0xFFFBD6C3); + Color get paleLeafGreen => const Color(0xFFBAD6C9); + Color get tropicalBlue => const Color(0xFFC7E0F1); + Color get bonePink => const Color(0xFFE6D7D0); + Color get lavaRed => const Color(0xFFD4351C); + Color get darkSpringGreen => const Color(0xFF00703C); + Color get curiousBlue => const Color(0xFF3498DB); + Color get waterBlue => const Color(0xFF048BD0); + Color get sunGlowYellow => const Color(0xFFFBC02D); + Color get darkOrchid => const Color(0xFF8E29BF); + Color get mangoOrange => const Color(0xFFEA8A3B); + Color get pacificBlue => const Color(0xFF0BABDE); + Color get hintGrey => const Color.fromRGBO(80, 90, 95, 1); + Color get orangeBG => const Color.fromRGBO(255, 250, 247, 1); + Color get transaparent => const Color.fromRGBO(0, 0, 0, 0); +} diff --git a/flutter/digit-ui-components/digit_components/lib/theme/digit_theme.dart b/flutter/digit-ui-components/digit_components/lib/theme/digit_theme.dart new file mode 100644 index 00000000000..d800c9ca701 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/theme/digit_theme.dart @@ -0,0 +1,200 @@ +library digit_theme; + +import 'package:digit_components/theme/colors.dart'; +import 'package:digit_components/theme/typography.dart'; +import 'package:flutter/material.dart'; + +const kPadding = 8.0; + +class DigitTheme { + static const DigitTheme _instance = DigitTheme._internal(); + + static DigitTheme get instance => _instance; + + DigitColors get colors => const DigitColors(); + + DigitMobileTypography get mobileTypography => DigitMobileTypography( + normalBase: const TextStyle( + fontFamily: 'Roboto', + ), + displayBase: const TextStyle( + fontFamily: 'Roboto', + ), + light: colors.davyGray, + normal: colors.woodsmokeBlack, + ); + + const DigitTheme._internal(); + + ThemeData get mobileTheme { + const Border(top: BorderSide()); + + return ThemeData( + colorScheme: colorScheme, + scaffoldBackgroundColor: colorScheme.background, + textTheme: mobileTypography.textTheme, + appBarTheme: const AppBarTheme(elevation: 0), + elevatedButtonTheme: elevatedButtonTheme, + outlinedButtonTheme: outlinedButtonTheme, + textButtonTheme: textButtonTheme, + cardTheme: cardTheme, + inputDecorationTheme: inputDecorationTheme, + dialogTheme: dialogTheme, + ); + } + + ColorScheme get colorScheme => ColorScheme( + brightness: Brightness.light, + primary: colors.regalBlue, + onPrimary: colors.white, + secondary: colors.burningOrange, + onSecondary: colors.white, + error: colors.lavaRed, + onError: colors.white, + background: colors.seaShellGray, + onBackground: colors.woodsmokeBlack, + surface: colors.alabasterWhite, + onSurface: colors.woodsmokeBlack, + onSurfaceVariant: colors.darkSpringGreen, + tertiaryContainer: colors.tropicalBlue, + inversePrimary: colors.paleLeafGreen, + surfaceTint: colors.waterBlue, + outline: colors.quillGray, + shadow: colors.davyGray, + tertiary: colors.paleRose, + onTertiaryContainer: colors.curiousBlue, + ); + + EdgeInsets get buttonPadding => const EdgeInsets.symmetric( + vertical: kPadding, + horizontal: kPadding * 2, + ); + + EdgeInsets get containerMargin => const EdgeInsets.all(kPadding); + + EdgeInsets get verticalMargin => const EdgeInsets.symmetric( + vertical: kPadding, + ); + + Duration get toastDuration => const Duration(seconds: 2); + + OutlinedBorder get buttonBorder => const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.zero), + ); + + ElevatedButtonThemeData get elevatedButtonTheme => ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + shape: buttonBorder, + padding: buttonPadding, + backgroundColor: colorScheme.secondary, + foregroundColor: colorScheme.onSecondary, + disabledBackgroundColor: colorScheme.secondary.withOpacity( + 0.5, + ), + disabledForegroundColor: colorScheme.onSecondary, + elevation: 0, + ), + ); + + OutlinedButtonThemeData get outlinedButtonTheme => OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: colorScheme.secondary, + side: BorderSide(color: colorScheme.secondary), + padding: buttonPadding, + ), + ); + + TextButtonThemeData get textButtonTheme => TextButtonThemeData( + style: TextButton.styleFrom( + shape: buttonBorder, + padding: buttonPadding, + textStyle: const TextStyle(fontSize: 16), + foregroundColor: colorScheme.secondary, + ), + ); + + CardTheme get cardTheme => const CardTheme( + margin: EdgeInsets.fromLTRB(kPadding, kPadding * 2, kPadding, 0), + elevation: 1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(4), + ), + ), + ); + + InputDecorationTheme get inputDecorationTheme => InputDecorationTheme( + enabledBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all( + Radius.circular( + 0, + ), + ), + borderSide: BorderSide( + color: colors.davyGray, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all( + Radius.circular( + 0, + ), + ), + borderSide: BorderSide( + color: colors.burningOrange, + width: 2, + ), + ), + disabledBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all( + Radius.circular( + 0, + ), + ), + borderSide: BorderSide(color: colors.cloudGray, width: 1), + ), + contentPadding: const EdgeInsets.all(12), + floatingLabelBehavior: FloatingLabelBehavior.never, + errorBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all( + Radius.circular( + 0, + ), + ), + borderSide: BorderSide( + color: colors.lavaRed, + ), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all( + Radius.circular( + 0, + ), + ), + borderSide: BorderSide(color: colors.lavaRed, width: 2), + ), + ); + + DialogTheme get dialogTheme => DialogTheme( + titleTextStyle: mobileTypography.headingL, + contentTextStyle: mobileTypography.bodyL, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular( + 4, + ), + ), + ), + actionsPadding: const EdgeInsets.all(kPadding), + ); + + BorderSide get tableCellBorder => BorderSide( + color: colorScheme.outline, + width: 0.5, + ); + + BorderSide get tableCellStrongBorder => BorderSide( + color: colorScheme.outline, + width: 2, + ); +} diff --git a/flutter/digit-ui-components/digit_components/lib/theme/theme.dart b/flutter/digit-ui-components/digit_components/lib/theme/theme.dart new file mode 100644 index 00000000000..e69bdb61a74 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/theme/theme.dart @@ -0,0 +1,3 @@ +export './typography.dart'; +export './digit_theme.dart'; +export './colors.dart'; diff --git a/flutter/digit-ui-components/digit_components/lib/theme/typography.dart b/flutter/digit-ui-components/digit_components/lib/theme/typography.dart new file mode 100644 index 00000000000..d87a7f51686 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/theme/typography.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; + +abstract class DigitTypography { + final Color? _textColorNormal; + final Color? _textColorLight; + final TextStyle _normalBase; + final TextStyle _displayBase; + + const DigitTypography({ + required TextStyle normalBase, + required TextStyle displayBase, + Color? normal, + Color? light, + }) : _normalBase = normalBase, + _displayBase = displayBase, + _textColorLight = light, + _textColorNormal = normal; + + TextStyle get _normal => + _normalBase.copyWith(color: _textColorNormal, fontFamily: 'Roboto'); + TextStyle get _light => + _normalBase.copyWith(color: _textColorLight, fontFamily: 'Roboto'); + TextStyle get _big => + _displayBase.copyWith(color: _textColorNormal, fontFamily: 'Roboto'); + + /// Follows Digit Typography standards + /// + /// + TextTheme get textTheme; + + /// Heading styles + TextStyle get headingXl => textTheme.displayMedium!; + TextStyle get headingL => textTheme.headlineLarge!; + TextStyle get headingM => textTheme.headlineMedium!; + TextStyle get headingS => textTheme.headlineSmall!; + + /// Caption styles + TextStyle get captionXL => textTheme.labelLarge!; + TextStyle get captionL => textTheme.labelMedium!; + TextStyle get captionM => textTheme.labelSmall!; + + /// Body styles + TextStyle get bodyL => textTheme.bodyLarge!; + TextStyle get bodyM => textTheme.bodyMedium!; + TextStyle get bodyS => textTheme.bodySmall!; + + /// Miscellaneous styles + TextStyle get label => textTheme.bodyLarge!; + TextStyle get link => textTheme.bodyLarge!; +} + +class DigitMobileTypography extends DigitTypography { + const DigitMobileTypography({ + required TextStyle normalBase, + required TextStyle displayBase, + Color? normal, + Color? light, + }) : super( + normal: normal, + light: light, + displayBase: displayBase, + normalBase: normalBase, + ); + + @override + TextTheme get textTheme { + return TextTheme( + displayMedium: _big.copyWith( + fontSize: 32, + fontWeight: FontWeight.w700, + fontFamily: 'Roboto Condensed', + ), + headlineLarge: _normal.copyWith( + fontSize: 24, + fontWeight: FontWeight.w700, + ), + headlineMedium: _normal.copyWith( + fontSize: 18, + fontWeight: FontWeight.w700, + ), + headlineSmall: _normal.copyWith( + fontSize: 16, + fontWeight: FontWeight.w700, + fontFamily: 'Roboto', + ), + bodyLarge: _normal.copyWith( + fontSize: 16, + fontWeight: FontWeight.w400, + fontFamily: 'Roboto', + ), + bodyMedium: _normal.copyWith( + fontSize: 14, + fontWeight: FontWeight.w400, + fontFamily: 'Roboto', + ), + bodySmall: _normal.copyWith( + fontSize: 12, + fontWeight: FontWeight.w400, + fontFamily: 'Roboto', + ), + labelLarge: _normal.copyWith( + fontSize: 19, + fontWeight: FontWeight.w500, + ), + labelMedium: _light.copyWith( + fontSize: 18, + fontWeight: FontWeight.w400, + fontFamily: 'Roboto', + ), + labelSmall: _normal.copyWith( + fontSize: 16, + fontWeight: FontWeight.w400, + fontFamily: 'Roboto', + ), + ); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/utils/date_utils.dart b/flutter/digit-ui-components/digit_components/lib/utils/date_utils.dart new file mode 100644 index 00000000000..4ad6e214c76 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/utils/date_utils.dart @@ -0,0 +1,175 @@ +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart'; + +class DigitDateUtils { + // Function to calculate age in years and months based on the selected date. + static DigitDOBAge calculateAge(DateTime selectedDate) { + DateTime currentDate = DateTime.now(); + + // Calculate the difference in years, months, and days + int ageInYears = currentDate.year - selectedDate.year; + int ageInMonths = currentDate.month - selectedDate.month; + int ageInDays = currentDate.day - selectedDate.day; + + // If the current day is earlier than the selected day in the same month, + // reduce the month count and adjust the days accordingly. + if (ageInDays < 0) { + ageInMonths--; + ageInDays += DateTime(selectedDate.year, selectedDate.month + 1, 0).day; + } + + // If the current month is earlier than the selected month, reduce the year count + // and adjust the month and day counts accordingly. + if (ageInMonths < 0) { + ageInYears--; + ageInMonths += 12; + } + + return DigitDOBAge( + years: ageInYears >= 0 ? ageInYears : 0, + months: ageInMonths, + days: ageInDays); + } + + // Function to get a formatted date string based on the provided date string and date format. + static getFilteredDate(String date, {String? dateFormat}) { + if (date.trim().isEmpty) return ''; + try { + var dateTime = DateTime.parse(date).toLocal(); + return DateFormat(dateFormat ?? "dd/MM/yyyy").format(dateTime); + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } + } + } + + static bool isLeapYear(int year) { + // A year is a leap year if it's divisible by 4 but not divisible by 100, + // or if it's divisible by 400. + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + } + + static int yearsToDays(int years) { + int days = 0; + for (int year = 0; year < years; year++) { + days += isLeapYear(year) ? 366 : 365; + } + return days; + } + + static int yearsMonthsDaysToDays(int years, int months, int days) { + int totalDays = 0; + + // Convert years to days + for (int year = 0; year < years; year++) { + totalDays += isLeapYear(year) ? 366 : 365; + } + + // Convert months to days + for (int month = 0; month < months; month++) { + totalDays += DateTime(DateTime.now().year, month + 2, 0).day; + } + + // Add the given days to the total + totalDays += days; + + return totalDays; + } + + // Function to parse the provided date string and return a DateTime object. + static DateTime? getFormattedDateToDateTime(String date) { + try { + DateFormat inputFormat; + inputFormat = date.contains('-') + ? DateFormat('dd-MM-yyyy') + : DateFormat('dd/MM/yyyy'); + DateTime inputDate = inputFormat.parse(date); + + return inputDate; + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } + + return null; + } + } + + // Function to get a formatted date string from the provided timestamp in milliseconds. + static String getDateFromTimestamp(int timestamp, {String? dateFormat}) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp); + return DateFormat(dateFormat ?? "dd/MM/yyyy").format(date); + } + + // Function to get time from timestamp. + static String getTimeFromTimestamp(int timestamp, + {bool is24HourFormat = false}) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp); + + return is24HourFormat + ? DateFormat('HH:mm').format(date) + : DateFormat('hh:mm a').format(date); + } + + // Function to convert a date string to a timestamp in milliseconds. + static int dateToTimeStamp(String dateTime) { + try { + return getFormattedDateToDateTime(dateTime)! + .toUtc() + .millisecondsSinceEpoch; + } catch (e) { + return 0; + } + } + + // Function to get the number of years or months between a provided date and the current date. + static getYearsAndMonthsFromDateTime( + DateTime dateTime, [ + bool getMonths = false, + ]) { + int days = DateTime.now().difference(dateTime).inDays; + int years = days ~/ 365; + int months = (days - (years * 365)) ~/ 30; + + return getMonths ? months : years; + } + + // Function to convert a timestamp to a formatted date string based on the provided format. + static String timeStampToDate(int? timeInMillis, {String? format}) { + if (timeInMillis == null) return ''; + try { + DateTime date = DateTime.fromMillisecondsSinceEpoch(timeInMillis); + return DateFormat(format ?? 'dd/MM/yyyy').format(date); + } catch (e) { + return e.toString(); + } + } + + // Function to get the abbreviated month name from the provided date. + static String getMonth(DateTime date) { + try { + return DateFormat.MMM().format(date); + } catch (e) { + return ''; + } + } + + // Function to get the abbreviated day name from the provided timestamp in milliseconds. + static String getDay(int timeInMillis) { + try { + DateTime date = DateTime.fromMillisecondsSinceEpoch(timeInMillis); + return DateFormat.E().format(date); + } catch (e) { + return ''; + } + } +} + +class DigitDOBAge { + final int years; + final int months; + final int days; + + DigitDOBAge({this.years = 0, this.months = 0, this.days = 0}); +} diff --git a/flutter/digit-ui-components/digit_components/lib/utils/time_utils.dart b/flutter/digit-ui-components/digit_components/lib/utils/time_utils.dart new file mode 100644 index 00000000000..35d0f08df04 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/utils/time_utils.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +String formatTime(TimeOfDay time) { + // Format the time as hh:mm am/pm + int hour = time.hourOfPeriod; + int minute = time.minute; + String period = time.period == DayPeriod.am ? 'AM' : 'PM'; + + String hourString = (hour % 12).toString().padLeft(2, '0'); + String minuteString = minute.toString().padLeft(2, '0'); + + return '$hourString:$minuteString $period'; +} \ No newline at end of file diff --git a/flutter/digit-ui-components/digit_components/lib/utils/validators/validator.dart b/flutter/digit-ui-components/digit_components/lib/utils/validators/validator.dart new file mode 100644 index 00000000000..07ef432484c --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/utils/validators/validator.dart @@ -0,0 +1,98 @@ +class InputValidators { + static String? validate(String? value, List validations) { + for (var validation in validations) { + final error = _applyValidation(value, validation); + if (error != null) { + return error; + } + } + return null; // Validation passed + } + + static String? _applyValidation(String? value, Validator validation) { + switch (validation.type) { + case ValidatorType.maxLength: + if (value != null && value.length > validation.value) { + return validation.errorMessage ?? 'Maximum length exceeded'; + } + break; + case ValidatorType.minLength: + if (value != null && value.length < validation.value) { + return validation.errorMessage ?? 'Minimum length not met'; + } + break; + case ValidatorType.pattern: + if (value != null && value.isNotEmpty && !RegExp(validation.value).hasMatch(value)) { + return validation.errorMessage ?? 'Invalid format'; + } + break; + case ValidatorType.mobileNumber: + if (value != null && value.isNotEmpty && !RegExp(r'^[0-9]{10}$').hasMatch(value)) { + return validation.errorMessage ?? 'Invalid mobile number'; + } + break; + case ValidatorType.email: + if (value != null && value.isNotEmpty && !RegExp(r'^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$').hasMatch(value)) { + return validation.errorMessage ?? 'Invalid email address'; + } + break; + case ValidatorType.customFunction: + if (validation.customValidation != null) { + final error = validation.customValidation!(value); + if (error != null) { + return error; + } + } + break; + case ValidatorType.required: + if (value == null || value.isEmpty) { + return validation.errorMessage ?? 'Field is required'; + } + break; + + case ValidatorType.minValue: + if (value != null) { + final intValue = int.tryParse(value); + + if (intValue == null || intValue < validation.value) { + return validation.errorMessage ?? 'Value is less than the minimum allowed'; + } + } + break; + case ValidatorType.maxValue: + if (value != null) { + final intValue = int.tryParse(value); + + if (intValue == null || intValue > validation.value) { + return validation.errorMessage ?? 'Value exceeds the maximum allowed'; + } + } + break; + + default: + return null; + } + return null; + } +} + +enum ValidatorType { + maxLength, + pattern, + minLength, + mobileNumber, + email, + customFunction, + required, + minValue, + maxValue, +} + +class Validator { + final ValidatorType type; + final dynamic? value; + final String? errorMessage; + final String? Function(String?)? customValidation; + + Validator(this.type, this.value, {this.errorMessage, this.customValidation}); +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_base_form_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_base_form_input.dart new file mode 100644 index 00000000000..89c020aab84 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_base_form_input.dart @@ -0,0 +1,469 @@ +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import '../../constants/AppView.dart'; +import '../../constants/app_constants.dart'; +import '../../utils/validators/validator.dart'; + +/// `BaseDigitFormInput` is a base class for different form input fields. It provides a set of customizable +/// parameters and common functionality for building various types of input fields within a form. +class BaseDigitFormInput extends StatefulWidget { + /// Text editing controller for the input field. + final TextEditingController controller; + + /// Determines if the input field is read-only. + final bool readOnly; + + /// Indicates whether the input field is disabled. + final bool isDisabled; + + /// Label displayed above the input field. + final String? label; + + /// Indicates whether additional information (tooltip) is available. + final bool? info; + + /// Additional information text for tooltip. + final String? infoText; + + /// Indicates whether to show character count. + final bool charCount; + + /// Inner label (hint) for the input field. + final String? innerLabel; + + /// Help text displayed below the input field. + final String? helpText; + + /// Icon to be displayed as a suffix in the input field. + final IconData? suffix; + + /// Determines when the tooltip should be triggered. + final TooltipTriggerMode triggerMode; + + /// Determines whether the tooltip should appear below the input field. + final bool preferToolTipBelow; + + /// Icon to be displayed as a suffix in the input field. + final IconData? suffixIcon; + + /// Icon to be displayed as a prefix in the input field. + final IconData? prefixIcon; + + /// Callback function triggered on validation error. + final void Function(String?)? onError; + + /// Callback function triggered on suffix icon tap. + final void Function(String)? onSuffixTap; + + /// Minimum number of lines for the input field. + final int minLine; + + /// Maximum number of lines for the input field. + final int maxLine; + + /// Preferred height of the input field. + final double height; + + /// Preferred width of the input field. + final double width; + + /// List of validation rules to be applied. + final List? validations; + + /// Callback function triggered on input value change. + final void Function(String)? onChange; + + /// Step value (used for specific input types like numbers). + final int step; + + /// Minimum allowed value (used for specific input types like numbers). + final int minValue; + + /// Maximum allowed value (used for specific input types like numbers). + final int maxValue; + + /// Determines whether the cursor should be visible in the input field. + final bool showCurser; + + /// Initial value for the input field. + final String? initialValue; + + /// Keyboard type for the input field. + final TextInputType keyboardType; + + /// Text alignment within the input field. + final TextAlign textAlign; + + const BaseDigitFormInput( + {Key? key, + required this.controller, + this.isDisabled = false, + this.readOnly = false, + this.initialValue, + this.label, + this.info, + this.infoText, + this.suffix, + this.charCount = false, + this.innerLabel, + this.helpText, + this.onError, + this.triggerMode = TooltipTriggerMode.tap, + this.preferToolTipBelow = false, + this.suffixIcon, + this.prefixIcon, + this.onSuffixTap, + this.minLine = 1, + this.maxLine = 1, + this.height = Default.height, + this.step = 1, + this.minValue = 0, + this.maxValue = 100, + this.showCurser = true, + this.width = Default.mobileInputWidth, + this.onChange, + this.keyboardType = TextInputType.text, + this.validations, + this.textAlign = TextAlign.start}) + : super(key: key); + + @override + BaseDigitFormInputState createState() => BaseDigitFormInputState(); +} + +class BaseDigitFormInputState extends State { + String? _value; + bool _hasError = false; + late FocusNode myFocusNode; + bool isVisible = false; + + String? _errorMessage; + + void onFocusChange() { + if (!myFocusNode.hasFocus) { + /// If the focus is lost, perform validation + setState(() { + _errorMessage = customValidator?.call(widget.controller.text); + _hasError = _errorMessage != null; + }); + + /// Call the provided onError function if there is an error + if (_hasError) { + widget.onError?.call(_errorMessage); + } + } + } + + @override + void initState() { + super.initState(); + + myFocusNode = FocusNode(); + + myFocusNode.addListener(onFocusChange); + + if (widget.initialValue != null) { + widget.controller.text = widget.initialValue!; + } + } + + @override + void dispose() { + super.dispose(); + myFocusNode.removeListener(onFocusChange); + myFocusNode.dispose(); + } + + String? customValidator(String? value) { + final validationError = InputValidators.validate( + value, + widget.validations ?? [], + ); + + setState(() { + _errorMessage = validationError; + _hasError = validationError != null; + }); + + return validationError; + } + + void toggleObsecureText() { + setState(() { + isVisible = !isVisible; + }); + } + + void onSuffixIconClick({void Function()? customFunction}) { + /// Call the provided function if it's not null + customFunction?.call(); + } + + void onPrefixIconClick({void Function()? customFunction}) { + /// Call the provided function if it's not null + customFunction?.call(); + } + + @override + Widget build(BuildContext context) { + int? getValidatorValue(List? validators, ValidatorType type) { + for (var validator in validators!) { + if (validator?.type == type) { + return validator?.value as int?; + } + } + return null; + } + + int? maxLengthValue = widget.charCount + ? (widget.validations != null + ? getValidatorValue(widget.validations, ValidatorType.maxLength) ?? + 64 + : 64) + : null; + + double inputWidth = AppView.isMobileView(MediaQuery.of(context).size.width) + ? Default.mobileInputWidth + : Default.desktopInputWidth; + + return SizedBox( + width: inputWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (widget?.label != null) + Text( + widget!.label!, + style: DigitTheme.instance.mobileTheme.textTheme.bodyLarge + ?.copyWith( + color: const DigitColors().woodsmokeBlack, + ), + ), + if (widget?.info == true) + Tooltip( + message: widget.infoText, + preferBelow: widget.preferToolTipBelow, + triggerMode: widget.triggerMode, + child: const Icon( + Icons.info_outline, + size: 16, + ), + ) + ], + ), + const Gap( + kPadding / 2, + ), + TextFormField( + focusNode: myFocusNode, + obscureText: isVisible, + controller: widget.controller, + readOnly: widget.readOnly, + enabled: !widget.isDisabled, + autovalidateMode: AutovalidateMode.disabled, + minLines: widget.minLine, + maxLines: widget.maxLine, + keyboardType: widget.keyboardType, + textAlign: widget.textAlign, + maxLength: maxLengthValue, + showCursor: widget.showCurser, + decoration: InputDecoration( + counterText: '', + hoverColor: const DigitColors().transaparent, + constraints: inputWidth == Default.mobileInputWidth + ? BoxConstraints( + maxHeight: widget.minLine > 1 + ? BaseConstants.inputMaxHeight + : BaseConstants.inputMinHeight, + minHeight: BaseConstants.inputMinHeight, + minWidth: BaseConstants.mobileInputMinWidth, + ) + : BoxConstraints( + maxHeight: widget.minLine > 1 + ? BaseConstants.inputMaxHeight + : BaseConstants.inputMinHeight, + minHeight: BaseConstants.inputMinHeight, + minWidth: BaseConstants.desktopInputMinWidth, + ), + contentPadding: widget.minLine > 1 + ? const EdgeInsets.all( + 12, + ) + : const EdgeInsets.symmetric( + vertical: kPadding / 2, + horizontal: 12, + ), + hintText: widget.innerLabel, + filled: true, + fillColor: widget.readOnly + ? const DigitColors().seaShellGray + : const DigitColors().transaparent, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: _hasError + ? const DigitColors().lavaRed + : const DigitColors().davyGray, + width: 1.0, + ), + borderRadius: BorderRadius.zero, + ), + focusedBorder: BaseConstants.focusedBorder, + disabledBorder: BaseConstants.disabledBorder, + prefixIconConstraints: widget.prefixIcon != null + ? const BoxConstraints( + maxWidth: 48, + maxHeight: 40, + ) + : null, + suffixIconConstraints: widget.suffixIcon != null + ? const BoxConstraints( + maxWidth: 48, + maxHeight: 40, + ) + : const BoxConstraints( + maxHeight: 24, + ), + suffixIcon: widget.suffixIcon != null + ? GestureDetector( + onTap: widget.readOnly ? null : onSuffixIconClick, + child: Container( + height: 38, + width: 40, + margin: const EdgeInsets.only( + left: kPadding, + right: 1, + ), + decoration: BoxDecoration( + color: const DigitColors().seaShellGray, + border: Border( + left: BorderSide( + color: widget.isDisabled + ? const DigitColors().cloudGray + : const DigitColors().davyGray, + width: 1.0, // specify the width of the border + ), + top: BorderSide.none, + bottom: BorderSide.none, + right: BorderSide.none, + ), + ), + child: Icon( + widget.suffixIcon!, + size: BaseConstants.suffixIconSize, + color: widget.isDisabled + ? const DigitColors().cloudGray + : const DigitColors().davyGray, + ), + ), + ) + : widget.suffix != null + ? GestureDetector( + onTap: widget.readOnly ? null : onSuffixIconClick, + child: Padding( + padding: const EdgeInsets.only( + right: kPadding, + ), + child: Icon( + isVisible == true + ? Icons.visibility_off + : widget.suffix, + size: BaseConstants.suffixIconSize, + ), + ), + ) + : null, + suffixIconColor: widget.isDisabled + ? const DigitColors().cloudGray + : const DigitColors().davyGray, + prefixIcon: widget.prefixIcon != null + ? GestureDetector( + onTap: widget.readOnly ? null : onPrefixIconClick, + child: Container( + height: 38, + width: 40, + margin: const EdgeInsets.only( + right: kPadding, + left: 1, + ), + decoration: BoxDecoration( + color: const DigitColors().seaShellGray, + border: Border( + right: BorderSide( + color: widget.isDisabled + ? const DigitColors().cloudGray + : const DigitColors().davyGray, + width: 1.0, // specify the width of the border + ), + top: BorderSide.none, + bottom: BorderSide.none, + left: BorderSide.none, + ), + ), + child: Icon( + widget.prefixIcon!, + size: BaseConstants.suffixIconSize, + color: widget.isDisabled + ? const DigitColors().cloudGray + : const DigitColors().davyGray, + ), + ), + ) + : null, + ), + onChanged: (value) { + setState(() { + _value = value; + }); + widget.onChange?.call(value); + }, + ), + const Gap( + kPadding / 2, + ), + if (widget.helpText != null || widget.charCount != null || _hasError) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (widget.helpText != null || _hasError) + _hasError + ? Row( + children: [ + Icon( + Icons.info, + color: const DigitColors().lavaRed, + size: BaseConstants.errorIconSize, + ), + Text( + _errorMessage!, + style: DigitTheme + .instance.mobileTheme.textTheme.bodyMedium + ?.copyWith( + color: const DigitColors().lavaRed, + ), + ), + ], + ) + : Text( + widget.helpText!, + style: DigitTheme + .instance.mobileTheme.textTheme.bodyMedium + ?.copyWith( + color: const DigitColors().davyGray, + ), + ), + if (widget.helpText == null && _hasError == false) + const Spacer(), + if (widget.charCount == true) + Text( + '${widget.controller.text.length ?? 0}/$maxLengthValue', + ), + ], + ), + ], + ), + ); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_button.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_button.dart new file mode 100644 index 00000000000..bc4ea27c3dc --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_button.dart @@ -0,0 +1,197 @@ +/*`DigitButton` is a customizable button widget that supports various styles and states. + + This widget provides options for primary, secondary, tertiary, and link button types. + It handles hover effects, disabled state, and different icon placements. + +Example usage: +```dart + DigitButton( + label: 'Click me', + onPressed: () { + // Handle button press + }, + type: ButtonType.primary, + prefixIcon: Icons.star, + suffixIcon: Icons.arrow_forward, + isDisabled: false, + buttonPadding: DigitButtonConstants.defaultButtonPadding, + contentPadding: DigitButtonConstants.defaultContentPadding, + iconSize: DigitButtonConstants.defaultIconSize, + ) + ```*/ + +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; +import '../../constants/app_constants.dart'; +import '../../enum/app_enums.dart'; + +class DigitButton extends StatefulWidget { + /// The text displayed on the button, representing the button's label or content. + final String label; + + /// Callback function invoked when the button is pressed. Defines the action to be performed. + final VoidCallback onPressed; + + /// Specifies the type or style of the button (primary, secondary, tertiary, or link). + final ButtonType type; + + /// Icon to be displayed before the label text. Can be null if no prefix icon is needed. + final IconData? prefixIcon; + + /// Icon to be displayed after the label text. Can be null if no suffix icon is needed. + final IconData? suffixIcon; + + /// Indicates whether the button is in a disabled state. If true, the button is disabled and cannot be interacted with. + final bool isDisabled; + + /// Padding around the entire button. Customizes the spacing between the button's content and its border. + final EdgeInsetsGeometry buttonPadding; + + /// Padding around the content of the button (label and icons). + final EdgeInsetsGeometry contentPadding; + + /// Size of the icons (prefixIcon and suffixIcon) displayed on the button in logical pixels. + final double iconSize; + + const DigitButton({ + Key? key, + required this.label, + required this.onPressed, + required this.type, + this.prefixIcon, + this.suffixIcon, + this.isDisabled = false, + this.buttonPadding = DigitButtonConstants.defaultButtonPadding, + this.contentPadding = DigitButtonConstants.defaultContentPadding, + this.iconSize = DigitButtonConstants.defaultIconSize, + }) : super(key: key); + + @override + _DigitButtonState createState() => _DigitButtonState(); +} + +class _DigitButtonState extends State { + bool isHovered = false; + + @override + Widget build(BuildContext context) { + return widget.isDisabled + ? _buildButtonWidget() + + /// If disabled, don't use MouseRegion + : MouseRegion( + onEnter: (_) { + setState(() { + isHovered = true; + }); + }, + onExit: (_) { + setState(() { + isHovered = false; + }); + }, + child: _buildButtonWidget(), + ); + } + + /// Build the button widget based on its type and state. + Widget _buildButtonWidget() { + if (widget.type == ButtonType.primary || + widget.type == ButtonType.secondary) { + return InkWell( + onTap: widget.isDisabled ? null : widget.onPressed, + splashColor: const DigitColors().transaparent, + hoverColor: const DigitColors().transaparent, + child: IntrinsicWidth( + child: Container( + padding: widget.buttonPadding, + decoration: BoxDecoration( + boxShadow: (widget.type == ButtonType.primary) + ? [ + BoxShadow( + color: isHovered + ? DigitButtonConstants.defaultPrimaryColor + : DigitButtonConstants.defaultSecondaryColor, + offset: const Offset(0, 2.0), + ), + ] + : [], + borderRadius: BorderRadius.zero, + border: Border.all( + color: widget.isDisabled + ? DigitButtonConstants.defaultDisabledColor + : DigitButtonConstants.defaultPrimaryColor, + width: isHovered + ? DigitButtonConstants.defaultHoverWidth + : DigitButtonConstants.defaultWidth, + ), + color: widget.type == ButtonType.primary + ? (widget.isDisabled + ? DigitButtonConstants.defaultDisabledColor + : DigitButtonConstants.defaultPrimaryColor) + : null, + ), + child: _buildButton(), + ), + ), + ); + } else { + return InkWell( + onTap: widget.isDisabled ? null : widget.onPressed, + hoverColor: const DigitColors().transaparent, + splashColor: const DigitColors().transaparent, + child: IntrinsicWidth( + child: _buildButton(), + ), + ); + } + } + + /// Build the content of the button, including label and icons. + Widget _buildButton() { + return Center( + child: Padding( + padding: widget.contentPadding, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (widget.prefixIcon != null) ...[ + Icon( + widget.prefixIcon, + size: widget.iconSize, + color: widget.type == ButtonType.primary + ? DigitButtonConstants.defaultTextColor + : (widget.isDisabled + ? DigitButtonConstants.defaultDisabledColor + : DigitButtonConstants.defaultPrimaryColor), + ), + ], + Text(widget.label, + style: DigitTheme.instance.mobileTheme.textTheme.bodyLarge + ?.copyWith( + color: widget.type == ButtonType.primary + ? DigitButtonConstants.defaultTextColor + : (widget.isDisabled + ? DigitButtonConstants.defaultDisabledColor + : DigitButtonConstants.defaultPrimaryColor), + decoration: widget.type == ButtonType.link + ? TextDecoration.underline + : null, + )), + if (widget.suffixIcon != null) ...[ + Icon( + widget.suffixIcon, + size: widget.iconSize, + color: widget.type == ButtonType.primary + ? DigitButtonConstants.defaultTextColor + : (widget.isDisabled + ? DigitButtonConstants.defaultDisabledColor + : DigitButtonConstants.defaultPrimaryColor), + ), + ], + ], + ), + ), + ); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_checkbox.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_checkbox.dart new file mode 100644 index 00000000000..345ecf4755e --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_checkbox.dart @@ -0,0 +1,121 @@ +/*? + `DigitCheckbox` is a checkbox component with a label and optional icon customization. + + This widget allows the user to toggle between checked and unchecked states. It supports + customization of the checkbox icon color, label, and disabled state. + + Example usage: + ```dart + DigitCheckbox( + label: 'CheckBox label', + onChanged: (value) { + // Handle checkbox state change + print('Feature X is now ${value ? 'enabled' : 'disabled'}'); + }, + disabled: false, // Set to true to disable the checkbox + value: true, // Set the initial state of the checkbox + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0), // Customize padding + iconColor: Colors.blue, // Customize checkbox icon color + ) + ...*/ + +import 'package:digit_components/digit_components.dart'; +import 'package:digit_components/widgets/atoms/digit_checkbox_icon.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import '../../constants/app_constants.dart'; +import '../../enum/app_enums.dart'; + +class DigitCheckbox extends StatefulWidget { + /// The current value of the checkbox. + final bool value; + + /// The label associated with the checkbox. + final String label; + + /// Callback function triggered when the checkbox value changes. + final ValueChanged? onChanged; + + /// Padding around the checkbox widget. + final EdgeInsetsGeometry padding; + + /// Indicates whether the checkbox is disabled or not. + final bool disabled; + + /// Custom color for the checkbox icon. + final Color? iconColor; + + /// Creates a `DigitCheckbox` widget with the given parameters. + const DigitCheckbox({ + Key? key, + required this.label, + this.onChanged, + this.disabled = false, + this.value = false, + this.padding = CheckboxConstants.defaultPadding, + this.iconColor, + }) : super(key: key); + + @override + _DigitCheckboxState createState() => _DigitCheckboxState(); +} + +class _DigitCheckboxState extends State { + late bool _currentState; + + @override + void initState() { + super.initState(); + _currentState = widget.value; + } + + @override + Widget build(BuildContext context) { + return IntrinsicWidth( + child: Padding( + padding: widget.padding, + child: InkWell( + hoverColor: const DigitColors().transaparent, + splashColor: const DigitColors().transaparent, + focusColor: const DigitColors().transaparent, + onTap: widget.disabled + ? null + : () { + setState(() { + _currentState = !_currentState; + }); + if (widget.onChanged != null) { + widget.onChanged!(_currentState); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + height: CheckboxConstants.containerSize, + width: CheckboxConstants.containerSize, + child: DigitCheckboxIcon( + state: _currentState + ? CheckboxState.checked + : CheckboxState.unchecked, + isDisabled: widget.disabled, + color: widget.iconColor, + ), + ), + const Gap(kPadding * 2), + Text( + widget.label, + style: DigitTheme.instance.mobileTheme.textTheme.bodyLarge + ?.copyWith( + color: widget.disabled + ? const DigitColors().cloudGray + : const DigitColors().woodsmokeBlack, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_checkbox_icon.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_checkbox_icon.dart new file mode 100644 index 00000000000..6bccc8f2da2 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_checkbox_icon.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import '../../constants/app_constants.dart'; +import '../../enum/app_enums.dart'; + +/// `DigitCheckboxIcon` is a widget that represents the visual appearance of a checkbox. +/// It supports three states: unchecked, intermediate, and checked. The appearance can be customized +/// based on the state, disabled status, and color. +class DigitCheckboxIcon extends StatelessWidget { + /// Represents the current state of the checkbox. + final CheckboxState state; + + /// Indicates whether the checkbox is disabled or not. + final bool isDisabled; + + /// Custom color for the checkbox. If not provided, default colors will be used based on the state and disabled status. + final Color? color; + + /// Creates a `DigitCheckboxIcon` widget with the given parameters. + const DigitCheckboxIcon({ + super.key, + required this.state, + this.isDisabled = false, + this.color, + }); + + @override + Widget build(BuildContext context) { + return _buildCheckbox(); + } + + /// Builds the checkbox widget based on its state. + Widget _buildCheckbox() { + switch (state) { + case CheckboxState.unchecked: + return Container( + width: CheckboxConstants.containerSize, + height: CheckboxConstants.containerSize, + decoration: BoxDecoration( + border: Border.all( + color: CheckboxConstants.uncheckedBorderColor( + isDisabled: isDisabled, customColor: color), + width: CheckboxConstants.borderWidth, + ), + borderRadius: BorderRadius.zero, + ), + ); + case CheckboxState.intermediate: + return Container( + width: CheckboxConstants.containerSize, + height: CheckboxConstants.containerSize, + decoration: BoxDecoration( + border: Border.all( + color: CheckboxConstants.intermediateBorderColor( + isDisabled: isDisabled, customColor: color), + width: CheckboxConstants.borderWidth, + ), + borderRadius: BorderRadius.zero, + ), + child: Center( + child: Icon( + Icons.square, + size: CheckboxConstants.iconSize, + color: CheckboxConstants.iconColor( + isDisabled: isDisabled, customColor: color), + ), + ), + ); + case CheckboxState.checked: + return Container( + width: CheckboxConstants.containerSize, + height: CheckboxConstants.containerSize, + decoration: BoxDecoration( + border: Border.all( + color: CheckboxConstants.checkedBorderColor( + isDisabled: isDisabled, customColor: color), + width: CheckboxConstants.borderWidth, + ), + borderRadius: BorderRadius.zero, + ), + child: Center( + child: Icon( + Icons.check, + size: CheckboxConstants.iconSize, + color: CheckboxConstants.iconColor( + isDisabled: isDisabled, customColor: color), + ), + ), + ); + } + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_date_form_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_date_form_input.dart new file mode 100644 index 00000000000..79cdb0533e8 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_date_form_input.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import '../../blocs/DateSelection.dart'; +import '../../utils/validators/validator.dart'; +import 'digit_base_form_input.dart'; + +/* + `DigitDateFormInput` is a customizable formfield widget that extends the baseforminput. + + Example usage: + ```dart + DigitDateFormInput( + controller: _textController, + label: 'Username', + innerLabel: 'click on the icon to choose a date', + charCount: true, + helpText: 'This is a simple example of DigitDateFormInput', + onChange: (value) { + print(value); + }, + ), + .... + */ + +class DigitDateFormInput extends BaseDigitFormInput { + const DigitDateFormInput({ + Key? key, + required TextEditingController controller, + String? label, + String? infoText, + bool? info, + String? initialValue, + bool readOnly = false, + bool isDisabled = false, + bool charCount = false, + String? innerLabel, + String? helpText, + TooltipTriggerMode triggerMode = TooltipTriggerMode.tap, + bool preferToolTipBelow = false, + IconData suffix = Icons.date_range, + void Function(String?)? onError, + final List? validations, + final void Function(String)? onChange, + }) : super( + key: key, + controller: controller, + label: label, + info: info, + infoText: infoText, + readOnly: readOnly, + isDisabled: isDisabled, + charCount: charCount, + innerLabel: innerLabel, + helpText: helpText, + triggerMode: triggerMode, + preferToolTipBelow: preferToolTipBelow, + onError: onError, + suffix: suffix, + initialValue: initialValue, + validations: validations, + onChange: onChange, + ); + + @override + _DigitDateFormInputState createState() => _DigitDateFormInputState(); +} + +class _DigitDateFormInputState extends BaseDigitFormInputState { + DateSelectionBloc dateSelectionBloc = DateSelectionBloc(); + + @override + void onSuffixIconClick({void Function()? customFunction}) async { + /// Show a date picker and update the controller's value + + await dateSelectionBloc.selectDate( + context: context, + controller: widget.controller, + ); + } + + @override + Widget build(BuildContext context) { + /// You can customize the appearance or behavior specific to the TextFormInput here + return super.build(context); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_dropdown_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_dropdown_input.dart new file mode 100644 index 00000000000..00025e8437f --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_dropdown_input.dart @@ -0,0 +1,615 @@ +/* +The DigitDropdownExample widget is a stateful widget that initializes a TextEditingController and a list of DropdownItem objects. +The DigitDropdown component is then used with the provided parameters, +and the onChange callback is used to handle the selected value. + + Example usage: + ```dart + DigitDropdown( + // Pass the TextEditingController + textEditingController: _textEditingController, + + // Pass the list of DropdownItems + items: _dropdownItems, + + // Callback function when an option is selected + onChange: (value, index) { + print('Selected: $value'); // Handle the selected value here + }, + + // Optional parameters can be customized as needed + icon: Icon(Icons.arrow_drop_down), + dropdownType: DropdownType.defaultSelect, + emptyItemText: 'No Options available', +) +....*/ + +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import '../../constants/AppView.dart'; +import '../../constants/app_constants.dart'; +import '../../enum/app_enums.dart'; +import '../../models/DropdownModels.dart'; + +class DigitDropdown extends StatefulWidget { + final TextEditingController textEditingController; + + /// onChange is called when the selected option is changed + /// It will pass back the value and the index of the option (can be different for different case). + final void Function(String, String) onChange; + + /// list of DropdownItems + final List items; + + /// used for text with icon + final IconData? textIcon; + + /// dropdown button icon defaults to caret + final Icon? icon; + + final DropdownType dropdownType; + + /// text to shown, when no options is available....... even while searching if no options matches + final String emptyItemText; + + const DigitDropdown({ + Key? key, + required this.items, + this.icon, + this.textIcon, + required this.onChange, + this.dropdownType = DropdownType.defaultSelect, + required this.textEditingController, + this.emptyItemText = "No Options available", + }) : super(key: key); + + @override + _DigitDropdownState createState() => _DigitDropdownState(); +} + +class _DigitDropdownState extends State> + with TickerProviderStateMixin { + final FocusNode _focusNode = FocusNode(); + final LayerLink _layerLink = LayerLink(); + late OverlayEntry _overlayEntry; + bool _isOpen = false; + int _currentIndex = -1; + String _nestedSelected = ''; + int _nestedIndex = -1; + late AnimationController _animationController; + late Animation _expandAnimation; + late Animation _rotateAnimation; + late List filteredItems; + late List _lastFilteredItems; + late List itemHoverStates; + + @override + void initState() { + super.initState(); + _focusNode.addListener(_onFocusChange); + filteredItems = List.from(widget.items); + _lastFilteredItems = List.from(widget.items); + itemHoverStates = List.generate(widget.items.length, (index) => false); + _animationController = AnimationController( + vsync: this, duration: DropdownConstants.animationDuration); + _expandAnimation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ); + _rotateAnimation = Tween(begin: 0.0, end: 0.5).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + )); + } + + @override + void dispose() { + _focusNode.removeListener(_onFocusChange); + _focusNode.dispose(); + + /// Dispose the AnimationController + _animationController.dispose(); + super.dispose(); + } + + void _onFocusChange() { + if (!_focusNode.hasFocus) { + _toggleDropdown(close: true); + } + } + + @override + Widget build(BuildContext context) { + /// Responsive width based on screen size + double dropdownWidth = + AppView.isMobileView(MediaQuery.of(context).size.width) + ? Default.mobileInputWidth + : Default.desktopInputWidth; + + /// link the overlay to the button + return CompositedTransformTarget( + link: this._layerLink, + child: SizedBox( + width: dropdownWidth, + height: Default.height, + child: TextField( + onTap: () { + _toggleDropdown(); + FocusScope.of(context).requestFocus(_focusNode); + }, + onChanged: (input) { + _filterItems(input); + if (!listEquals(filteredItems, _lastFilteredItems)) { + _updateOverlay(); + _lastFilteredItems = filteredItems; + } + }, + focusNode: _focusNode, + controller: widget.textEditingController, + decoration: InputDecoration( + border: const OutlineInputBorder( + borderRadius: BorderRadius.zero, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: const DigitColors().woodsmokeBlack, width: 1.0), + borderRadius: BorderRadius.zero, + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: const DigitColors().burningOrange, width: 1.0), + borderRadius: BorderRadius.zero, + ), + contentPadding: const EdgeInsets.only( + left: 8, + ), + suffixIcon: RotationTransition( + turns: _rotateAnimation, + child: widget.icon ?? const Icon(Icons.arrow_drop_down), + ), + suffixIconColor: const DigitColors().davyGray, + ), + ), + ), + ); + } + + /// function to filter items based on the input + void _filterItems(String input) { + List newFilteredItems = widget.items + .where((item) => + item.name + .trim() + .toLowerCase() + .contains(input.trim().toLowerCase()) || + (item.description != null && + item.description! + .trim() + .toLowerCase() + .contains(input.trim().toLowerCase()))) + .toList(); + + if (!listEquals(newFilteredItems, _lastFilteredItems)) { + setState(() { + filteredItems = newFilteredItems; + }); + } + } + + /// update the overlay when overlay needs to build again + void _updateOverlay() { + if (_isOpen && _overlayEntry != null) { + _overlayEntry!.remove(); + _overlayEntry = _createOverlayEntry(); + Overlay.of(context)!.insert(_overlayEntry!); + } + } + + ///overlay for dropdown + OverlayEntry _createOverlayEntry() { + /// find the size and position of the current widget + RenderBox? renderBox = context?.findRenderObject() as RenderBox?; + + if (renderBox == null) { + /// Handle the case where renderBox is null (e.g., widget not yet laid out) + return OverlayEntry(builder: (context) => const SizedBox.shrink()); + } + var size = renderBox.size; + + var offset = renderBox.localToGlobal(Offset.zero); + var topOffset = offset.dy + size.height; + OverlayEntry overlayEntry = OverlayEntry( + /// full screen GestureDetector to register when a user has clicked away from the dropdown + builder: (context) => GestureDetector( + onTap: () => _toggleDropdown(close: true), + behavior: HitTestBehavior.translucent, + + /// full screen SizedBox to register taps anywhere and close drop down + child: SizedBox( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + child: Stack( + children: [ + Positioned( + left: offset.dx, + top: topOffset, + width: size.width, + child: CompositedTransformFollower( + offset: Offset(0, size.height), + link: this._layerLink, + showWhenUnlinked: false, + child: Material( + elevation: 0, + borderRadius: BorderRadius.zero, + color: const DigitColors().white, + clipBehavior: Clip.none, + child: SizeTransition( + axisAlignment: 1, + sizeFactor: _expandAnimation, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height - + topOffset - + 15, + ), + child: _buildDropdownListView(), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + return overlayEntry; + } + + /// build the dropdown based on the type + Widget _buildDropdownListView() { + switch (widget.dropdownType) { + case DropdownType.defaultSelect: + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: _buildListView(), + ); + case DropdownType.profileSelect: + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: _buildListView(), + ); + case DropdownType.nestedSelect: + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: _buildNestedListView(), + ); + } + } + + Widget _buildListView() { + return ListView( + padding: EdgeInsets.zero, + shrinkWrap: true, + children: filteredItems.isNotEmpty + ? filteredItems.asMap().entries.map((item) { + Color backgroundColor = item.key % 2 == 0 + ? const DigitColors().white + : const DigitColors().alabasterWhite; + + return StatefulBuilder( + builder: (context, setState) { + return InkWell( + splashColor: const DigitColors().transaparent, + hoverColor: const DigitColors().transaparent, + onHover: (hover) { + setState(() { + itemHoverStates[item.key] = hover; + }); + }, + onTap: () { + setState(() => _currentIndex = item.key); + widget.onChange(item.value.name, 'selected'); + _toggleDropdown(); + }, + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: itemHoverStates[item.key] + ? const DigitColors().burningOrange + : Colors.transparent, + ), + color: itemHoverStates[item.key] + ? const DigitColors().orangeBG + : backgroundColor, + ), + padding: EdgeInsets.zero, + child: Padding( + padding: + widget.dropdownType == DropdownType.defaultSelect && + item.value.description == null + ? DropdownConstants.defaultPadding + : DropdownConstants.nestedItemPadding, + child: Row( + children: [ + if (widget.dropdownType == + DropdownType.profileSelect) + SizedBox( + height: DropdownConstants.defaultProfileSize, + width: DropdownConstants.defaultProfileSize, + child: CircleAvatar( + radius: DropdownConstants.defaultImageRadius, + + /// This radius is the radius of the picture in the circle avatar itself. + backgroundImage: item.value.profileImage, + backgroundColor: const DigitColors().davyGray, + ), + ), + if (widget.dropdownType == + DropdownType.profileSelect) + const Gap( + 6, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (item.value.textIcon != null) + Icon( + item.value.textIcon, + size: DropdownConstants.textIconSize, + color: const DigitColors().davyGray, + ), + if (item.value.textIcon != null) + const Gap( + kPadding / 2, + ), + Text( + item.value.name, + style: DigitTheme.instance.mobileTheme + .textTheme.bodyLarge + ?.copyWith( + color: + const DigitColors().davyGray), + ) + ], + ), + if (item.value.description != null) + Text( + item.value.description!, + style: DigitTheme.instance.mobileTheme + .textTheme.bodySmall + ?.copyWith( + color: const DigitColors().davyGray, + ), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + }).toList() + : [ + Center( + child: Padding( + padding: DropdownConstants.noItemAvailablePadding, + child: Text(widget.emptyItemText), + ), + ), + ], + ); + } + + Widget _buildNestedListView() { + return ListView( + padding: EdgeInsets.zero, + shrinkWrap: true, + children: _buildGroupedItems(), + ); + } + + List _buildGroupedItems() { + List groupedItems = []; + Set uniqueTypes = filteredItems.map((item) => item.type).toSet(); + + for (String? type in uniqueTypes) { + if (type != null) { + /// header for the type + groupedItems.add( + Container( + padding: DropdownConstants.nestedItemHeaderPadding, + color: const DigitColors().alabasterWhite, + child: Text(type, + style: DigitTheme.instance.mobileTheme.textTheme.headlineSmall + ?.copyWith( + color: const DigitColors().davyGray, + )), + ), + ); + + /// items of the current type + List typeItems = + filteredItems.where((item) => item.type == type).toList(); + + for (DropdownItem item in typeItems) { + groupedItems.add( + StatefulBuilder( + builder: (context, setState) { + return Column( + children: [ + InkWell( + splashColor: const DigitColors().transaparent, + hoverColor: const DigitColors().transaparent, + onHover: (hover) { + setState(() { + itemHoverStates[typeItems.indexOf(item)] = hover; + }); + }, + onTap: () { + setState(() { + _nestedSelected = '$type,${item.name}'; + _nestedIndex = 1; + }); + widget.onChange(item.name, type); + _toggleDropdown(); + }, + child: Container( + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + border: Border.all( + color: itemHoverStates[typeItems.indexOf(item)] + ? const DigitColors().burningOrange + : Colors.transparent, + ), + color: itemHoverStates[typeItems.indexOf(item)] + ? const DigitColors().orangeBG + : const DigitColors().white, + ), + padding: EdgeInsets.zero, + child: Padding( + padding: widget.dropdownType == + DropdownType.defaultSelect && + item.description == null + ? DropdownConstants.defaultPadding + : DropdownConstants.nestedItemPadding, + child: Row( + children: [ + if (widget.dropdownType == + DropdownType.profileSelect) + SizedBox( + height: DropdownConstants.defaultProfileSize, + width: DropdownConstants.defaultProfileSize, + child: CircleAvatar( + radius: + DropdownConstants.defaultImageRadius, + + /// This radius is the radius of the picture in the circle avatar itself. + backgroundImage: item.profileImage, + backgroundColor: + const DigitColors().davyGray, + ), + ), + if (widget.dropdownType == + DropdownType.profileSelect) + const Gap( + 6, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (item.textIcon != null) + Icon( + item.textIcon, + size: DropdownConstants.textIconSize, + color: const DigitColors().davyGray, + ), + if (item.textIcon != null) + const Gap( + kPadding / 2, + ), + Text( + item.name, + style: DigitTheme.instance.mobileTheme + .textTheme.bodyLarge + ?.copyWith( + color: const DigitColors() + .davyGray), + ) + ], + ), + if (item.description != null) + Text( + item.description!, + style: DigitTheme.instance.mobileTheme + .textTheme.bodySmall + ?.copyWith( + color: const DigitColors().davyGray, + ), + ), + ], + ), + ], + ), + ), + ), + ), + + /// Divider after each option + Container( + height: 2, + color: const DigitColors().quillGray, + width: MediaQuery.of(context).size.width, + margin: const EdgeInsets.symmetric( + horizontal: 10, + ), + ) + + /// Divider after each option + ], + ); + }, + ), + ); + } + } + } + + /// Add a message if no options are available + if (groupedItems.isEmpty) { + groupedItems.add( + Center( + child: Padding( + padding: DropdownConstants.noItemAvailablePadding, + child: Text(widget.emptyItemText), + ), + ), + ); + } + + return groupedItems; + } + + /// function to make change when the dropdown is opening or closing.... we will reset and set the value inside this + void _toggleDropdown({bool close = false}) async { + if (_isOpen || close) { + await _animationController.reverse(); + _overlayEntry?.remove(); + setState(() { + _isOpen = false; + }); + } else { + setState(() { + _currentIndex = -1; + + /// Reset the index when opening the dropdown + _nestedIndex = -1; + + /// Reset the index when opening the dropdown + _overlayEntry = _createOverlayEntry(); + }); + Overlay.of(context).insert(_overlayEntry!); + setState(() => _isOpen = true); + _animationController.forward(); + } + if (_currentIndex != -1) { + setState(() { + widget.textEditingController.text = filteredItems[_currentIndex].name; + }); + } + if (widget.dropdownType == DropdownType.nestedSelect && + _nestedIndex != -1) { + setState(() { + widget.textEditingController.text = _nestedSelected; + }); + } + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_location_form_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_location_form_input.dart new file mode 100644 index 00000000000..1daa98d0ec4 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_location_form_input.dart @@ -0,0 +1,79 @@ +/* +DigitLocationFormInput` is a customizable formfield widget that extends the baseforminput. + + Example usage: + ```dart + DigitLocationFormInput( + controller: _textController, + label: 'Username', + innerLabel: 'Click on the icon to add your current location', + charCount: true, + helpText: 'This is a simple example of DigitLocationFormInput', + onChange: (value) { + print(value); + }, + ), + ....*/ + +import 'package:flutter/material.dart'; +import '../../blocs/LocationBloc.dart'; +import '../../utils/validators/validator.dart'; +import 'digit_base_form_input.dart'; + +class DigitLocationFormInput extends BaseDigitFormInput { + const DigitLocationFormInput({ + Key? key, + required TextEditingController controller, + String? label, + String? infoText, + bool? info, + bool readOnly = false, + bool isDisabled = false, + String? initialValue, + bool charCount = false, + String? innerLabel, + String? helpText, + TooltipTriggerMode triggerMode = TooltipTriggerMode.tap, + bool preferToolTipBelow = false, + IconData suffix = Icons.my_location, + void Function(String?)? onError, + final List? validations, + final void Function(String)? onChange, + }) : super( + key: key, + controller: controller, + label: label, + info: info, + infoText: infoText, + readOnly: readOnly, + isDisabled: isDisabled, + charCount: charCount, + innerLabel: innerLabel, + helpText: helpText, + triggerMode: triggerMode, + preferToolTipBelow: preferToolTipBelow, + suffix: suffix, + onError: onError, + initialValue: initialValue, + validations: validations, + onChange: onChange, + ); + + @override + _DigitLocationFormInputState createState() => _DigitLocationFormInputState(); +} + +class _DigitLocationFormInputState extends BaseDigitFormInputState { + LocationBloc locationBloc = LocationBloc(); + + @override + void onSuffixIconClick({void Function()? customFunction}) async { + await locationBloc.getCurrentLocation(widget.controller); + } + + @override + Widget build(BuildContext context) { + /// You can customize the appearance or behavior specific to the TextFormInput here + return super.build(context); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_multiselect_dropdown.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_multiselect_dropdown.dart new file mode 100644 index 00000000000..c6fc3b81323 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_multiselect_dropdown.dart @@ -0,0 +1,788 @@ +library multiselect_dropdown; +/* +the MultiSelectDropDown component is used to create a multi-select dropdown with a list of options. +The onOptionSelected callback is used to handle the selected items, and the appearance of the dropdown, chips, +and other elements can be customized using various properties. + + Example usage: + ```dart + MultiSelectDropDown( + // Provide the list of options + options: options, + // Provide the initially selected options + selectedOptions: selectedOptions, + // Define the callback function when options are selected/deselected + onOptionSelected: (List selectedItems) { + // Handle the selected items + setState(() { + selectedOptions = selectedItems; + }); + }, + // Customize the appearance of chips + chipConfig: ChipConfig( + labelStyle: TextStyle(color: Colors.white), + backgroundColor: Colors.blue, + deleteIconColor: Colors.white, + ), + // Customize the suffix icon (dropdown arrow) + suffixIcon: Icon(Icons.arrow_drop_down), + // Customize the selection type (multiSelect or singleSelect) + selectionType: SelectionType.multiSelect, + ), + ....*/ + +import 'package:collection/collection.dart'; +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import '../../constants/AppView.dart'; +import '../../constants/app_constants.dart'; +import '../../enum/app_enums.dart'; +import '../../models/DropdownModels.dart'; +import '../../models/chipModel.dart'; +import '../helper_widget/selection_chip.dart'; +import 'digit_checkbox_icon.dart'; + +typedef OnOptionSelected = void Function(List selectedOptions); + +class MultiSelectDropDown extends StatefulWidget { + /// selection type of the dropdown + final SelectionType selectionType; + + /// Options + final List options; + final List selectedOptions; + final OnOptionSelected? onOptionSelected; + + /// chip configuration + final ChipConfig chipConfig; + + /// dropdownfield configuration + final Icon? suffixIcon; + + /// focus node + final FocusNode? focusNode; + + /// Controller for the dropdown + /// [controller] is the controller for the dropdown. It can be used to programmatically open and close the dropdown. + final MultiSelectController? controller; + + const MultiSelectDropDown({ + Key? key, + required this.onOptionSelected, + required this.options, + this.chipConfig = const ChipConfig(), + this.selectionType = SelectionType.multiSelect, + this.selectedOptions = const [], + this.suffixIcon = const Icon(Icons.arrow_drop_down), + this.focusNode, + this.controller, + }) : super(key: key); + + @override + State> createState() => + _MultiSelectDropDownState(); +} + +class _MultiSelectDropDownState extends State> { + /// Options list that is used to display the options. + final List _options = []; + + /// Selected options list that is used to display the selected options. + final List _selectedOptions = []; + + /// The controller for the dropdown. + OverlayState? _overlayState; + OverlayEntry? _overlayEntry; + bool _selectionMode = false; + + late final FocusNode _focusNode; + final LayerLink _layerLink = LayerLink(); + + /// value notifier that is used for controller. + MultiSelectController? _controller; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _initialize(); + }); + _focusNode = widget.focusNode ?? FocusNode(); + _controller = widget.controller ?? MultiSelectController(); + } + + void _initialize() { + if (!mounted) return; + _addOptions(); + _overlayState ??= Overlay.of(context); + _focusNode.addListener(_handleFocusChange); + } + + /// Adds the selected options and disabled options to the options list. + void _addOptions() { + setState(() { + _selectedOptions.addAll(_controller?.selectedOptions.isNotEmpty == true + ? _controller!.selectedOptions + : widget.selectedOptions); + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_controller != null && _controller?._isDisposed == false) { + _controller!.setOptions(_options); + _controller!.setSelectedOptions(_selectedOptions); + _controller!.addListener(_handleControllerChange); + } + }); + } + + /// Handles the focus change to show/hide the dropdown. + _handleFocusChange() { + if (_focusNode.hasFocus && mounted) { + _overlayEntry = _buildOverlayEntry(); + Overlay.of(context).insert(_overlayEntry!); + // return; + } + + if (_focusNode.hasFocus == false && _overlayEntry != null) { + _overlayEntry?.remove(); + } + + if (mounted) { + setState(() { + _selectionMode = _focusNode.hasFocus == true; + }); + } + + if (_controller != null) { + _controller!.value._isDropdownOpen = _focusNode.hasFocus == true; + } + } + + /// Handles the widget rebuild when the options are changed externally. + @override + void didUpdateWidget(covariant MultiSelectDropDown oldWidget) { + if (widget.controller == null && oldWidget.controller != null) { + _controller = MultiSelectController(); + } else if (widget.controller != null && oldWidget.controller == null) { + _controller!.dispose(); + _controller = null; + } + + /// If the options are changed externally, then the options are updated. + if (listEquals(widget.options, oldWidget.options) == false) { + _options.clear(); + _options.addAll(widget.options); + + /// If the controller is not null, then the options are updated in the controller. + if (_controller != null) { + _controller!.setOptions(_options); + } + } + + /// If the selected options are changed externally, then the selected options are updated. + if (listEquals(widget.selectedOptions, oldWidget.selectedOptions) == + false) { + _selectedOptions.clear(); + _selectedOptions.addAll(widget.options + .where((element) => widget.selectedOptions.contains(element))); + + /// If the controller is not null, then the selected options are updated in the controller. + if (_controller != null) { + _controller!.setSelectedOptions(_selectedOptions); + } + } + + super.didUpdateWidget(oldWidget); + } + + /// Calculate offset size for dropdown. + List _calculateOffsetSize() { + RenderBox? renderBox = context.findRenderObject() as RenderBox?; + + var size = renderBox?.size ?? Size.zero; + var offset = renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + + final availableHeight = MediaQuery.of(context).size.height - offset.dy; + + return [size, availableHeight]; + } + + @override + Widget build(BuildContext context) { + double dropdownWidth = + AppView.isMobileView(MediaQuery.of(context).size.width) + ? Default.mobileInputWidth + : Default.desktopInputWidth; + return Column( + children: [ + CompositedTransformTarget( + link: _layerLink, + child: Focus( + canRequestFocus: true, + skipTraversal: true, + focusNode: _focusNode, + child: InkWell( + splashColor: const DigitColors().transaparent, + highlightColor: const DigitColors().transaparent, + hoverColor: const DigitColors().transaparent, + onTap: _toggleFocus, + child: StatefulBuilder(builder: (context, setState) { + return Container( + height: Default.height, + width: dropdownWidth, + constraints: const BoxConstraints( + minWidth: Default.mobileInputWidth, + minHeight: Default.height, + ), + padding: const EdgeInsets.symmetric( + horizontal: kPadding, + ), + decoration: _getContainerDecoration(), + child: Row( + children: [ + Expanded( + child: (_selectedOptions.isNotEmpty) + ? Text('${_selectedOptions.length} Selected') + : const Text(''), + ), + AnimatedRotation( + turns: _selectionMode ? 0.5 : 0, + duration: DropdownConstants.animationDuration, + child: widget.suffixIcon, + ), + ], + ), + ); + }), + ), + ), + ), + const Gap( + kPadding, + ), + if (_anyItemSelected) + SizedBox( + width: dropdownWidth, + child: _getContainerContent(), + ) + ], + ); + } + + /// Container Content for the dropdown. + Widget _getContainerContent() { + if (_selectedOptions.isEmpty) { + return const Text('select items to show here'); + } + + return _buildSelectedItems(); + } + + /// return true if any item is selected. + bool get _anyItemSelected => _selectedOptions.isNotEmpty; + + /// Container decoration for the dropdown. + Decoration _getContainerDecoration() { + return BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.zero, + border: _selectionMode + ? Border.all( + color: const DigitColors().burningOrange, + width: 1, + ) + : Border.all( + color: const DigitColors().davyGray, + width: 1, + ), + ); + } + + /// Dispose the focus node and overlay entry. + @override + void dispose() { + if (_overlayEntry?.mounted == true) { + if (_overlayState != null && _overlayEntry != null) { + _overlayEntry?.remove(); + } + _overlayEntry = null; + _overlayState?.dispose(); + } + _focusNode.removeListener(_handleFocusChange); + _focusNode.dispose(); + _controller?.removeListener(_handleControllerChange); + + if (widget.controller == null || widget.controller?.isDisposed == true) { + _controller!.dispose(); + } + + super.dispose(); + } + + /// Handle the focus change on tap outside of the dropdown. + void _onOutSideTap() { + _focusNode.unfocus(); + } + + /// Method to toggle the focus of the dropdown. + void _toggleFocus() { + if (_focusNode.hasFocus) { + _focusNode.unfocus(); + } else { + _focusNode.requestFocus(); + } + } + + /// Create the overlay entry for the dropdown. + OverlayEntry _buildOverlayEntry() { + /// Calculate the offset and the size of the dropdown button + final values = _calculateOffsetSize(); + + /// Get the size from the first item in the values list + final size = values[0] as Size; + + return OverlayEntry(builder: (context) { + List options = widget.options; + List selectedOptions = [..._selectedOptions]; + + return StatefulBuilder(builder: ((context, dropdownState) { + return Stack( + children: [ + Positioned.fill( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: _onOutSideTap, + child: Container( + color: Colors.transparent, + ), + ), + ), + CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + targetAnchor: Alignment.bottomLeft, + followerAnchor: Alignment.topLeft, + offset: Offset.zero, + child: Material( + borderRadius: BorderRadius.zero, + shadowColor: null, + child: SizedBox( + width: size.width, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + widget.selectionType == SelectionType.nestedMultiSelect + ? _buildNestedOptions( + values, options, selectedOptions, dropdownState) + : _buildFlatOptions( + values, options, selectedOptions, dropdownState), + ], + ), + ), + ), + ), + ], + ); + })); + }); + } + + Widget _buildFlatOptions(List values, List options, + List selectedOptions, StateSetter dropdownState) { + return ConstrainedBox( + constraints: BoxConstraints( + maxHeight: values[1] - 30, + ), + child: ListView.separated( + separatorBuilder: (_, __) => const SizedBox(height: 0), + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: options.length, + itemBuilder: (context, index) { + final option = options[index]; + bool isSelected = selectedOptions.any( + (item) => item.code == option.code && item.name == option.name); + Color backgroundColor = index % 2 == 0 + ? const DigitColors().white + : const DigitColors().alabasterWhite; + return _buildOption( + option, + isSelected, + dropdownState, + backgroundColor, + selectedOptions, + ); + }, + ), + ); + } + + Widget _buildNestedOptions(List values, List options, + List selectedOptions, StateSetter dropdownState) { + /// Group options by type + final groupedOptions = groupBy(options, (option) => option.type); + + return ConstrainedBox( + constraints: BoxConstraints( + maxHeight: values[1] - 30, + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: groupedOptions.length, + itemBuilder: (context, index) { + final type = groupedOptions.keys.elementAt(index); + final typeOptions = groupedOptions[type] ?? []; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (type != null) + Container( + padding: const EdgeInsets.all(10), + color: const DigitColors().alabasterWhite, + child: Text( + type, + style: DigitTheme + .instance.mobileTheme.textTheme.headlineSmall + ?.copyWith( + color: const DigitColors().davyGray, + ), + ), + ), + ...typeOptions.map((option) { + bool isSelected = selectedOptions.any((item) => + item.code == option.code && item.name == option.name); + Color backgroundColor = options.indexOf(option) % 2 == 0 + ? const DigitColors().white + : const DigitColors().alabasterWhite; + + return _buildOption( + option, + isSelected, + dropdownState, + backgroundColor, + selectedOptions, + ); + }).toList(), + ], + ); + }, + ), + ); + } + + Column _buildOption( + DropdownItem option, + bool isSelected, + StateSetter dropdownState, + Color backgroundColor, + List selectedOptions) { + return Column( + children: [ + ListTile( + splashColor: const DigitColors().transaparent, + focusColor: const DigitColors().transaparent, + hoverColor: const DigitColors().transaparent, + title: Row( + children: [ + if (isSelected) + DigitCheckboxIcon( + state: CheckboxState.checked, + color: const DigitColors().white, + ), + if (!isSelected) + const DigitCheckboxIcon(state: CheckboxState.unchecked), + const Gap( + 10, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (option.textIcon != null) + Icon( + option.textIcon, + size: DropdownConstants.textIconSize, + color: isSelected + ? const DigitColors().white + : const DigitColors().davyGray, + ), + if (option.textIcon != null) + const Gap( + kPadding / 2, + ), + Text( + option.name, + style: DigitTheme + .instance.mobileTheme.textTheme.headlineSmall + ?.copyWith( + color: isSelected + ? const DigitColors().white + : const DigitColors().davyGray, + ), + ), + ], + ), + if (option.description != null) + Text( + option.description!, + style: DigitTheme.instance.mobileTheme.textTheme.bodySmall + ?.copyWith( + color: isSelected + ? const DigitColors().white + : const DigitColors().davyGray, + ), + ), + ], + ), + ], + ), + textColor: const DigitColors().davyGray, + selectedColor: const DigitColors().white, + selected: isSelected, + autofocus: true, + tileColor: widget.selectionType == SelectionType.nestedMultiSelect + ? const DigitColors().white + : backgroundColor, + selectedTileColor: const DigitColors().burningOrange, + contentPadding: const EdgeInsets.symmetric(horizontal: 10), + onTap: () { + if (isSelected) { + dropdownState(() { + selectedOptions.remove(option); + }); + setState(() { + _selectedOptions.remove(option); + }); + } else { + dropdownState(() { + selectedOptions.add(option); + }); + setState(() { + _selectedOptions.add(option); + }); + } + + if (_controller != null) { + _controller!.value._selectedOptions.clear(); + _controller!.value._selectedOptions.addAll(_selectedOptions); + } + widget.onOptionSelected?.call(_selectedOptions); + }, + ), + Container( + height: 2, + color: const DigitColors().quillGray, + width: MediaQuery.of(context).size.width, + margin: const EdgeInsets.only( + left: 10, + right: 10, + ), + ) + ], + ); + } + + /// Clear the selected options. + /// [MultiSelectController] is used to clear the selected options. + void clear() { + if (_controller != null && !_controller!._isDisposed) { + _controller!.clearAllSelection(); + } else { + setState(() { + _selectedOptions.clear(); + }); + widget.onOptionSelected?.call(_selectedOptions); + } + if (_focusNode.hasFocus) _focusNode.unfocus(); + } + + /// Build the selected items for the dropdown. + Widget _buildSelectedItems() { + return Wrap( + spacing: widget.chipConfig.spacing, + runSpacing: widget.chipConfig.runSpacing, + children: [ + ..._selectedOptions.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + + Widget chip = _buildChip(item, widget.chipConfig); + + return chip; + }), + if (_selectedOptions.isNotEmpty) + + /// Display "Clear All" only if there are selected options + InkWell( + onTap: () => clear(), + child: Chip( + backgroundColor: const DigitColors().white, + shape: RoundedRectangleBorder( + side: BorderSide( + color: const DigitColors().burningOrange, + ), + borderRadius: BorderRadius.circular(50), + ), + padding: const EdgeInsets.symmetric(horizontal: kPadding), + labelPadding: const EdgeInsets.only(top: 2, bottom: 2), + label: Text('Clear All', + style: TextStyle(color: const DigitColors().burningOrange)), + ), + ), + ], + ); + } + + /// Build the selected item chip. + Widget _buildChip(DropdownItem item, ChipConfig chipConfig) { + return SelectionChip( + item: item, + selectionType: widget.selectionType, + chipConfig: chipConfig, + onItemDelete: (removedItem) { + if (_controller != null) { + _controller!.clearSelection(removedItem); + } else { + setState(() { + _selectedOptions.remove(removedItem); + }); + widget.onOptionSelected?.call(_selectedOptions); + } + if (_focusNode.hasFocus) _focusNode.unfocus(); + }, + ); + } + + /// handle the controller change. + void _handleControllerChange() { + /// if the controller is null, return. + if (_controller == null || _controller?.isDisposed == true) return; + + /// if current options are not equal to the controller's options, update the state. + if (_options != _controller!.value._options) { + setState(() { + _options.clear(); + _options.addAll(_controller!.value._options); + }); + } + + /// if current selected options are not equal to the controller's selected options, update the state. + if (_selectedOptions != _controller!.value._selectedOptions) { + setState(() { + _selectedOptions.clear(); + _selectedOptions.addAll(_controller!.value._selectedOptions); + }); + widget.onOptionSelected?.call(_selectedOptions); + } + + if (_selectionMode != _controller!.value._isDropdownOpen) { + if (_controller!.value._isDropdownOpen) { + _focusNode.requestFocus(); + } else { + _focusNode.unfocus(); + } + } + } +} + +/// MultiSelect Controller class. +/// This class is used to control the state of the MultiSelectDropdown widget. +/// This is just base class. The implementation of this class is in the MultiSelectController class. +/// The implementation of this class is hidden from the user. +class _MultiSelectController { + final List _options = []; + final List _selectedOptions = []; + bool _isDropdownOpen = false; +} + +/// implementation of the MultiSelectController class. +class MultiSelectController + extends ValueNotifier<_MultiSelectController> { + MultiSelectController() : super(_MultiSelectController()); + + bool _isDisposed = false; + + bool get isDisposed => _isDisposed; + + /// set the dispose method. + @override + void dispose() { + super.dispose(); + _isDisposed = true; + } + + /// Clear the selected options. + /// [MultiSelectController] is used to clear the selected options. + void clearAllSelection() { + value._selectedOptions.clear(); + notifyListeners(); + } + + /// clear specific selected option + /// [MultiSelectController] is used to clear specific selected option. + void clearSelection(DropdownItem option) { + if (!value._selectedOptions.contains(option)) return; + value._selectedOptions.remove(option); + notifyListeners(); + } + + /// select the options + /// [MultiSelectController] is used to select the options. + void setSelectedOptions(List options) { + if (options.any((element) => !value._options.contains(element))) { + throw Exception('Cannot select options that are not in the options list'); + } + + value._selectedOptions.clear(); + value._selectedOptions.addAll(options); + notifyListeners(); + } + + /// add selected option + /// [MultiSelectController] is used to add selected option. + void addSelectedOption(DropdownItem option) { + if (!value._options.contains(option)) { + throw Exception('Cannot select option that is not in the options list'); + } + + value._selectedOptions.add(option); + notifyListeners(); + } + + /// set options + /// [MultiSelectController] is used to set options. + void setOptions(List options) { + value._options.clear(); + value._options.addAll(options); + notifyListeners(); + } + + /// get options + List get options => value._options; + + /// get selected options + List get selectedOptions => value._selectedOptions; + + /// get is dropdown open + bool get isDropdownOpen => value._isDropdownOpen; + + /// show dropdown + /// [MultiSelectController] is used to show dropdown. + void showDropdown() { + if (value._isDropdownOpen) return; + value._isDropdownOpen = true; + notifyListeners(); + } + + /// hide dropdown + /// [MultiSelectController] is used to hide dropdown. + void hideDropdown() { + if (!value._isDropdownOpen) return; + value._isDropdownOpen = false; + notifyListeners(); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_numeric_form_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_numeric_form_input.dart new file mode 100644 index 00000000000..c47c9ebeff0 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_numeric_form_input.dart @@ -0,0 +1,110 @@ +/* + `DigitNumericFormInput` is a customizable formfield widget that extends the baseforminput. + + Example usage: + ```dart + DigitNumericFormInput( + controller: _textController, + label: 'Username', + innerLabel: 'Enter a numeric value', + charCount: true, + helpText: 'This is a simple example of DigitNumericFormInput', + validations: [ + // Example validation: Required field + Validator(ValidatorType.required), + ], + onChange: (value) { + print(value); + }, + ), + ....*/ + +import 'package:flutter/material.dart'; +import '../../utils/validators/validator.dart'; +import 'digit_base_form_input.dart'; + +class DigitNumericFormInput extends BaseDigitFormInput { + const DigitNumericFormInput({ + Key? key, + required TextEditingController controller, + String? label, + String? infoText, + bool? info, + String? initialValue, + bool readOnly = false, + bool isDisabled = false, + bool charCount = false, + String? innerLabel, + String? helpText, + TooltipTriggerMode triggerMode = TooltipTriggerMode.tap, + bool preferToolTipBelow = false, + IconData suffixIcon = Icons.add, + IconData prefixIcon = Icons.remove, + void Function(String?)? onError, + TextInputType keyboardType = TextInputType.number, + TextAlign textAlign = TextAlign.center, + final List? validations, + final void Function(String)? onChange, + final int step = 5, + final int minValue = 0, + final int maxValue = 100, + }) : super( + key: key, + controller: controller, + label: label, + info: info, + infoText: infoText, + readOnly: readOnly, + isDisabled: isDisabled, + charCount: charCount, + innerLabel: innerLabel, + helpText: helpText, + triggerMode: triggerMode, + preferToolTipBelow: preferToolTipBelow, + suffixIcon: suffixIcon, + prefixIcon: prefixIcon, + onError: onError, + keyboardType: keyboardType, + textAlign: textAlign, + initialValue: initialValue, + validations: validations, + onChange: onChange, + step: step, + maxValue: maxValue, + minValue: minValue, + showCurser: false, + ); + + @override + _DigitNumericFormInputState createState() => _DigitNumericFormInputState(); +} + +class _DigitNumericFormInputState extends BaseDigitFormInputState { + @override + void onPrefixIconClick({void Function()? customFunction}) { + setState(() { + // Subtract step from the input value when the prefix icon is clicked + int currentValue = int.tryParse(widget.controller.text) ?? 0; + if ((currentValue - widget.step) >= widget.minValue) { + widget.controller.text = (currentValue - widget.step).toString(); + } + }); + } + + @override + void onSuffixIconClick({void Function()? customFunction}) { + setState(() { + /// Add step to the input value when the suffix icon is clicked + int currentValue = int.tryParse(widget.controller.text) ?? 0; + if ((currentValue + widget.step) <= widget.maxValue) { + widget.controller.text = (currentValue + widget.step).toString(); + } + }); + } + + @override + Widget build(BuildContext context) { + /// You can customize the appearance or behavior specific to the TextFormInput here + return super.build(context); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_password_form_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_password_form_input.dart new file mode 100644 index 00000000000..bb76f74cdd0 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_password_form_input.dart @@ -0,0 +1,93 @@ +/* + `DigitPasswordFormInput` is a customizable formfield widget that extends the baseforminput. + + Example usage: + ```dart + DigitPasswordFormInput( + controller: _textController, + label: 'Username', + innerLabel: 'Please enter a valid password', + charCount: true, + helpText: 'This is a simple example of DigitPasswordFormInput', + onChange: (value) { + print(value); + }, + ), + ....*/ + +import 'package:flutter/material.dart'; +import '../../utils/validators/validator.dart'; +import 'digit_base_form_input.dart'; + +class DigitPasswordFormInput extends BaseDigitFormInput { + const DigitPasswordFormInput({ + Key? key, + required TextEditingController controller, + String? label, + String? infoText, + bool? info, + String? initialValue, + bool readOnly = false, + bool isDisabled = false, + bool charCount = false, + String? innerLabel, + String? helpText, + TooltipTriggerMode triggerMode = TooltipTriggerMode.tap, + bool preferToolTipBelow = false, + IconData suffix = Icons.visibility, + void Function(String?)? onError, + void Function(String)? onSuffixTap, + final List? validations, + final void Function(String)? onChange, + }) : super( + key: key, + controller: controller, + label: label, + info: info, + infoText: infoText, + readOnly: readOnly, + isDisabled: isDisabled, + charCount: charCount, + innerLabel: innerLabel, + helpText: helpText, + triggerMode: triggerMode, + preferToolTipBelow: preferToolTipBelow, + suffix: suffix, + onError: onError, + onSuffixTap: onSuffixTap, + initialValue: initialValue, + validations: validations, + onChange: onChange, + ); + + @override + _DigitPasswordFormInputState createState() => _DigitPasswordFormInputState(); +} + +class _DigitPasswordFormInputState extends BaseDigitFormInputState { + @override + void initState() { + /// Call the initState method of the base class + super.initState(); + + /// Set the initial value of isVisible to true + isVisible = true; + } + + @override + void toggleObsecureText() { + /// Call the base class implementation + super.toggleObsecureText(); + } + + @override + void onSuffixIconClick({void Function()? customFunction}) { + toggleObsecureText(); + } + + @override + Widget build(BuildContext context) { + /// You can customize the appearance or behavior specific to the TextFormInput here + return super.build(context); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_radio_list.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_radio_list.dart new file mode 100644 index 00000000000..5126732a105 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_radio_list.dart @@ -0,0 +1,188 @@ +/* + `DigitRadioList` is a widget for rendering a list of radio buttons. + This widget provides options for radio buttons, handling hover effects, and a disabled state. + + Example usage: + ```dart + DigitRadioList( + radioButtons: [ + RadioButtonModel(code: 'option1', name: 'Option 1'), + RadioButtonModel(code: 'option2', name: 'Option 2'), + ], + onChanged: (selectedValue) { + // Handle radio button selection + }, + groupValue: 'option1', // can be passed same to select value initially + isDisabled: false, + ) + ....*/ + +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; + +import '../../constants/AppView.dart'; +import '../../constants/app_constants.dart'; +import '../../models/RadioButtonModel.dart'; + +class DigitRadioList extends StatefulWidget { + /// List of RadioButtonModel objects representing the radio buttons + final List radioButtons; + + /// Callback function to be called when a radio button is selected + final void Function(String selectedValue) onChanged; + + /// Currently selected value in the radio button group + String groupValue; + + /// Flag to indicate if the radio buttons are disabled + final bool isDisabled; + + /// container padding + final EdgeInsetsGeometry containerPadding; + + /// radio button width + final double radioWidth; + + /// radio button height + final double radioHeight; + + /// Constructor for the DigitRadioList widget + DigitRadioList({ + Key? key, + required this.radioButtons, + required this.onChanged, + this.groupValue = '', + this.isDisabled = false, + this.containerPadding = RadioConstant.defaultPadding, + this.radioWidth = RadioConstant.radioWidth, + this.radioHeight = RadioConstant.radioHeight, + }) : super(key: key); + + /// Create the state for the widget + @override + _DigitRadioListState createState() => _DigitRadioListState(); +} + +/// State class for the DigitRadioList widget +class _DigitRadioListState extends State { + /// List to track whether each radio button is being hovered over + late List isHoveredList; + + /// Initialize the state + @override + void initState() { + super.initState(); + + /// Initialize the hover list with false values + isHoveredList = List.generate(widget.radioButtons.length, (index) => false); + } + + /// Build the widget based on screen width + @override + Widget build(BuildContext context) { + if (AppView.isMobileView(MediaQuery.of(context).size.width)) { + /// Mobile view layout + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: _buildRadioButtons(), + ); + } else { + /// Tablet or desktop view layout + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: _buildRadioButtons(), + ); + } + } + + List _buildRadioButtons() { + return widget.radioButtons.map( + (button) { + final index = widget.radioButtons.indexOf(button); + return Padding( + padding: widget.containerPadding, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + MouseRegion( + onEnter: (_) { + setState(() { + isHoveredList[index] = true; + }); + }, + onExit: (_) { + setState(() { + isHoveredList[index] = false; + }); + }, + child: GestureDetector( + onTap: widget.isDisabled + ? null + : () { + setState(() { + /// Update the selected value and call the onChanged callback + widget.groupValue == button.code + ? widget.groupValue = '' + : widget.groupValue = button.code; + }); + widget.onChanged!(widget.groupValue); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(kPadding / 2), + width: widget.radioWidth, + height: widget.radioHeight, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: widget.isDisabled + ? const DigitColors().cloudGray + : (widget.groupValue == button.code || + isHoveredList[index]) + ? const DigitColors().burningOrange + : const DigitColors().davyGray, + width: 1.0, + ), + color: const DigitColors().transaparent, + ), + child: widget.groupValue == button.code + ? Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: widget.isDisabled + ? const DigitColors().cloudGray + : const DigitColors().burningOrange, + ), + ) + : null, + ), + const Gap( + kPadding, + ), + Align( + alignment: Alignment.center, + child: Text( + button.name, + style: DigitTheme + .instance.mobileTheme.textTheme.bodyLarge + ?.copyWith( + color: widget.isDisabled + ? const DigitColors().cloudGray + : const DigitColors().woodsmokeBlack, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + }, + ).toList(); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_search_form_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_search_form_input.dart new file mode 100644 index 00000000000..21c93121870 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_search_form_input.dart @@ -0,0 +1,78 @@ +/* + `DigitSearchFormInput` is a customizable form-field widget that extends the baseforminput. + + Example usage: + ```dart + DigitSearchFormInput( + controller: _textController, + label: 'Search', + innerLabel: 'Enter your username', + charCount: true, + helpText: 'This is a simple example of DigitSearchFormInput', + onChange: (value) { + print(value); + }, + onSuffixTap(value){} + ), + ....*/ + +import 'package:flutter/material.dart'; +import '../../utils/validators/validator.dart'; +import 'digit_base_form_input.dart'; + +class DigitSearchFormInput extends BaseDigitFormInput { + const DigitSearchFormInput({ + Key? key, + required TextEditingController controller, + String? label, + String? infoText, + bool? info, + bool charCount = false, + String? innerLabel, + String? helpText, + bool readOnly = false, + bool isDisabled = false, + TooltipTriggerMode triggerMode = TooltipTriggerMode.tap, + bool preferToolTipBelow = false, + IconData suffix = Icons.search, + void Function(String?)? onError, + void Function(String)? onSuffixTap, + final List? validations, + final void Function(String)? onChange, + }) : super( + key: key, + controller: controller, + label: label, + info: info, + infoText: infoText, + readOnly: readOnly, + isDisabled: isDisabled, + charCount: charCount, + innerLabel: innerLabel, + helpText: helpText, + triggerMode: triggerMode, + preferToolTipBelow: preferToolTipBelow, + suffix: suffix, + onError: onError, + onSuffixTap: onSuffixTap, + validations: validations, + onChange: onChange, + ); + + @override + _DigitSearchFormInputState createState() => _DigitSearchFormInputState(); +} + +class _DigitSearchFormInputState extends BaseDigitFormInputState { + @override + void onSuffixIconClick({void Function()? customFunction}) { + /// Use the onTap function directly + widget.onSuffixTap?.call(widget.controller.text); + } + + @override + Widget build(BuildContext context) { + /// You can customize the appearance or behavior specific to the TextFormInput here + return super.build(context); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_text_area_form_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_text_area_form_input.dart new file mode 100644 index 00000000000..9604e811b52 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_text_area_form_input.dart @@ -0,0 +1,75 @@ +/* +/// `DigitTextAreaFormInput` is a customizable formfield widget that extends the baseforminput. + + Example usage: + ```dart + DigitTextAreaFormInput( + controller: _textController, + label: 'Description', + innerLabel: 'Enter your details', + charCount: true, + helpText: 'This is a simple example of DigitTextAreaFormInput', + onChange: (value) { + print(value); + }, + ), + ....*/ + +import 'package:flutter/material.dart'; +import '../../utils/validators/validator.dart'; +import 'digit_base_form_input.dart'; + +class DigitTextAreaFormInput extends BaseDigitFormInput { + const DigitTextAreaFormInput({ + Key? key, + required TextEditingController controller, + String? label, + String? initialValue, + String? infoText, + bool? info, + bool readOnly = false, + bool isDisabled = false, + bool charCount = false, + String? innerLabel, + String? helpText, + TooltipTriggerMode triggerMode = TooltipTriggerMode.tap, + bool preferToolTipBelow = false, + void Function(String?)? onError, + final List? validations, + int maxLine = 4, + int minLine = 4, + double height = 100, + final void Function(String)? onChange, + }) : super( + key: key, + controller: controller, + label: label, + info: info, + infoText: infoText, + readOnly: readOnly, + isDisabled: isDisabled, + charCount: charCount, + innerLabel: innerLabel, + helpText: helpText, + triggerMode: triggerMode, + preferToolTipBelow: preferToolTipBelow, + onError: onError, + maxLine: maxLine, + minLine: minLine, + initialValue: initialValue, + height: height, + validations: validations, + onChange: onChange, + ); + + @override + _DigitTextAreaFormInputState createState() => _DigitTextAreaFormInputState(); +} + +class _DigitTextAreaFormInputState extends BaseDigitFormInputState { + @override + Widget build(BuildContext context) { + /// You can customize the appearance or behavior specific to the TextFormInput here + return super.build(context); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_text_form_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_text_form_input.dart new file mode 100644 index 00000000000..badcbdc37e3 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_text_form_input.dart @@ -0,0 +1,77 @@ +/* + `DigitTextFormInput` is a customizable textformfield widget that extends the baseforminput. + + Example usage: + ```dart + DigitTextFormInput( + controller: _textController, + label: 'Username', + innerLabel: 'Enter your username', + charCount: true, + helpText: 'This is a simple example of DigitTextFormInput', + validations: [ + // Example validation: Required field + Validator(ValidatorType.required), + ], + onChange: (value) { + print('Username changed to: $value'); + }, + ), + ....*/ + +import 'package:flutter/material.dart'; +import '../../utils/validators/validator.dart'; +import 'digit_base_form_input.dart'; + +class DigitTextFormInput extends BaseDigitFormInput { + const DigitTextFormInput({ + Key? key, + required TextEditingController controller, + String? label, + String? initialValue, + String? infoText, + bool? info, + bool readOnly = false, + bool isDisabled = false, + bool charCount = false, + String? innerLabel, + String? helpText, + TooltipTriggerMode triggerMode = TooltipTriggerMode.tap, + bool preferToolTipBelow = false, + IconData? suffixIcon, + IconData? prefixIcon, + void Function(String?)? onError, + final List? validations, + final void Function(String)? onChange, + }) : super( + key: key, + controller: controller, + label: label, + info: info, + infoText: infoText, + readOnly: readOnly, + isDisabled: isDisabled, + charCount: charCount, + innerLabel: innerLabel, + helpText: helpText, + triggerMode: triggerMode, + preferToolTipBelow: preferToolTipBelow, + suffixIcon: suffixIcon, + prefixIcon: prefixIcon, + onError: onError, + initialValue: initialValue, + validations: validations, + onChange: onChange, + ); + + @override + _DigitTextFormInputState createState() => _DigitTextFormInputState(); +} + +class _DigitTextFormInputState extends BaseDigitFormInputState { + @override + Widget build(BuildContext context) { + /// You can customize the appearance or behavior specific to the TextFormInput here + return super.build(context); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_time_form_input.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_time_form_input.dart new file mode 100644 index 00000000000..a2875523867 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_time_form_input.dart @@ -0,0 +1,83 @@ +/* + `DigitTextFormInput` is a customizable textformfield widget that extends the baseforminput + + Example usage: + ```dart + DigitTimeFormInput( + controller: _textController, + label: 'Time Picker', + innerLabel: 'click on the icon to select a time', + charCount: true, + helpText: 'This is a simple example of DigitTimeFormInput', + onChange: (value) { + print(value); + }, + ), + ....*/ + +import 'package:flutter/material.dart'; +import '../../blocs/TimeSelectionBloc.dart'; +import '../../utils/time_utils.dart'; +import '../../utils/validators/validator.dart'; +import 'digit_base_form_input.dart'; + +class DigitTimeFormInput extends BaseDigitFormInput { + const DigitTimeFormInput({ + Key? key, + required TextEditingController controller, + String? label, + String? infoText, + bool? info, + bool charCount = false, + String? innerLabel, + String? helpText, + bool readOnly = false, + bool isDisabled = false, + TooltipTriggerMode triggerMode = TooltipTriggerMode.tap, + bool preferToolTipBelow = false, + IconData suffix = Icons.access_time, + void Function(String?)? onError, + final List? validations, + String? initialValue, + final void Function(String)? onChange, + }) : super( + key: key, + controller: controller, + label: label, + info: info, + infoText: infoText, + readOnly: readOnly, + isDisabled: isDisabled, + charCount: charCount, + innerLabel: innerLabel, + helpText: helpText, + triggerMode: triggerMode, + preferToolTipBelow: preferToolTipBelow, + suffix: suffix, + onError: onError, + initialValue: initialValue, + validations: validations, + onChange: onChange, + ); + + @override + _DigitTimeFormInputState createState() => _DigitTimeFormInputState(); +} + +class _DigitTimeFormInputState extends BaseDigitFormInputState { + @override + void onSuffixIconClick({void Function()? customFunction}) async { + TimeSelectionBloc timeSelectionBloc = TimeSelectionBloc(); + + await timeSelectionBloc.selectTime( + context: context, + controller: widget.controller, + ); + } + + @override + Widget build(BuildContext context) { + /// You can customize the appearance or behavior specific to the TextFormInput here + return super.build(context); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_toggle.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_toggle.dart new file mode 100644 index 00000000000..a8d1fea9d76 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_toggle.dart @@ -0,0 +1,80 @@ +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; +/// DigitToggle is a custom toggle button widget that provides visual feedback on hover and supports selection. + +class DigitToggle extends StatefulWidget { + final void Function(bool isSelected) onChanged; + final String label; + bool isSelected; + + DigitToggle({ + Key? key, + required this.onChanged, + required this.label, + this.isSelected = false, + }) : super(key: key); + + @override + _DigitToggleState createState() => _DigitToggleState(); +} + +class _DigitToggleState extends State { + bool isHovered = false; + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerLeft, + child: MouseRegion( + onEnter: (_) { + setState(() { + isHovered = true; + }); + }, + onExit: (_) { + setState(() { + isHovered = false; + }); + }, + child: GestureDetector( + onTap: () { + widget.onChanged(true); + }, + child: Container( + height: 32, + width: 112, + constraints: const BoxConstraints( + minWidth: 40, + maxWidth: 200, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.zero, + border: Border.all( + color: (isHovered || widget.isSelected) + ? const DigitColors().burningOrange + : const DigitColors().cloudGray, + width: 1.0, + ), + color: widget.isSelected + ? const DigitColors().burningOrange + : Colors.transparent, + ), + child: Center( + child: Text( + widget.label, + textAlign: TextAlign.center, + style: DigitTheme.instance.mobileTheme.textTheme.bodyMedium?.copyWith( + color: (isHovered && !widget.isSelected) + ? const DigitColors().burningOrange + : widget.isSelected + ? const DigitColors().white + : const DigitColors().cloudGray, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_toggle_list.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_toggle_list.dart new file mode 100644 index 00000000000..4e59213629f --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_toggle_list.dart @@ -0,0 +1,84 @@ +/* + DigitToggleList is a row of toggle buttons that allows selecting one item at a time. + + Example usage: + ```dart + DigitToggleList( + toggleButtons: [ + ToggleButtonModel(name: 'Option 1', onSelected: () => print('Option 1 selected')), + ToggleButtonModel(name: 'Option 2', onSelected: () => print('Option 2 selected')), + // Add more ToggleButtonModel instances as needed + ], + onChanged: (selectedValues) { + // Handle the selected values if needed + print('Selected values: $selectedValues'); + }, + contentPadding: EdgeInsets.symmetric(horizontal: 16), // Optional content padding +) + ....*/ + +import 'package:flutter/material.dart'; +import '../../models/toggleButtonModel.dart'; +import 'digit_toggle.dart'; + +class DigitToggleList extends StatefulWidget { + final List toggleButtons; + final void Function(List selectedValues) onChanged; + final EdgeInsets? contentPadding; + + const DigitToggleList({ + Key? key, + required this.toggleButtons, + required this.onChanged, + this.contentPadding, + }) : super(key: key); + + @override + _DigitToggleListState createState() => _DigitToggleListState(); +} + +class _DigitToggleListState extends State { + int? selectedIndex; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: widget.toggleButtons.map( + (button) { + final index = widget.toggleButtons.indexOf(button); + return Padding( + padding: widget.contentPadding ?? const EdgeInsets.only(bottom: 8), + child: DigitToggle( + onChanged: (isSelected) { + setState(() { + if (isSelected) { + if (selectedIndex != null && selectedIndex == index) { + /// Clicked on the already selected item, unselect it + selectedIndex = null; + } else { + /// Unselect the previously selected item + if (selectedIndex != null) { + widget.toggleButtons[selectedIndex!].onSelected?.call(); + } + selectedIndex = index; + } + } else { + selectedIndex = null; + } + }); + + /// Check if the button is selected and has a callback + if (isSelected && button.onSelected != null) { + button.onSelected!(); + } + }, + label: button.name, + isSelected: selectedIndex == index, + ), + ); + }, + ).toList(), + ); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_tree_select_dropdown.dart b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_tree_select_dropdown.dart new file mode 100644 index 00000000000..80f63f86b7c --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/atoms/digit_tree_select_dropdown.dart @@ -0,0 +1,671 @@ +/* + The TreeSelectDropDown component is a Flutter dropdown widget with a tree-like structure, allowing the selection of multiple or single options + It initializes the options, selected options, and other configurations. + It supports the integration of a TreeSelectController for programmatically controlling the dropdown. + It provides customization options for the appearance of the dropdown, chips, and other elements. + + Example usage: + ```dart + +TreeSelectDropDown( + // Provide the list of options + options: options, + // Provide the initially selected options + selectedOptions: selectedOptions, + // Define the callback function when options are selected/deselected + onOptionSelected: (List selectedItems) { + // Handle the selected items + setState(() { + selectedOptions = selectedItems; + }); + }, + // Customize the appearance of chips + chipConfig: ChipConfig(), + // Customize the suffix icon (dropdown arrow) + suffixIcon: Icon(Icons.arrow_drop_down), + // Customize the selection type (multiSelect or singleSelect) + treeSelectionType: TreeSelectionType.multiSelect, +), + ....*/ + +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import '../../constants/AppView.dart'; +import '../../constants/app_constants.dart'; +import '../../enum/app_enums.dart'; +import '../../models/TreeModel.dart'; +import '../../models/chipModel.dart'; +import '../helper_widget/selection_chip.dart'; +import '../helper_widget/tree_node_widget.dart'; + +typedef OnOptionSelected = void Function(List selectedOptions); + +class TreeSelectDropDown extends StatefulWidget { + /// selection type of the dropdown + final TreeSelectionType treeSelectionType; + + /// Options + final List options; + final List selectedOptions; + final OnOptionSelected? onOptionSelected; + + final ChipConfig chipConfig; + + /// dropdownfield configuration + final Icon? suffixIcon; + final Decoration? inputDecoration; + + /// focus node + final FocusNode? focusNode; + + /// Controller for the dropdown + /// [controller] is the controller for the dropdown. It can be used to programmatically open and close the dropdown. + final TreeSelectController? controller; + + const TreeSelectDropDown({ + Key? key, + required this.onOptionSelected, + required this.options, + this.treeSelectionType = TreeSelectionType.MultiSelect, + this.selectedOptions = const [], + this.chipConfig = const ChipConfig(), + this.suffixIcon = const Icon(Icons.arrow_drop_down), + this.inputDecoration, + this.focusNode, + this.controller, + }) : super(key: key); + + @override + State> createState() => + _TreeSelectDropDownState(); +} + +class _TreeSelectDropDownState extends State> { + /// Options list that is used to display the options. + final List _options = []; + + /// Selected options list that is used to display the selected options. + final List _selectedOptions = []; + + /// The controller for the dropdown. + OverlayState? _overlayState; + OverlayEntry? _overlayEntry; + bool _selectionMode = false; + + late final FocusNode _focusNode; + final LayerLink _layerLink = LayerLink(); + + /// value notifier that is used for controller. + TreeSelectController? _controller; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _initialize(); + }); + _focusNode = widget.focusNode ?? FocusNode(); + _controller = widget.controller ?? TreeSelectController(); + } + + void _initialize() { + if (!mounted) return; + _addOptions(); + _overlayState ??= Overlay.of(context); + _focusNode.addListener(_handleFocusChange); + } + + /// Adds the selected options and disabled options to the options list. + void _addOptions() { + setState(() { + _selectedOptions.addAll(_controller?.selectedOptions.isNotEmpty == true + ? _controller!.selectedOptions + : widget.selectedOptions); + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_controller != null && _controller?._isDisposed == false) { + _controller!.setOptions(_options); + _controller!.setSelectedOptions(_selectedOptions); + _controller!.addListener(_handleControllerChange); + } + }); + } + + /// Handles the focus change to show/hide the dropdown. + _handleFocusChange() { + if (_focusNode.hasFocus && mounted) { + _overlayEntry = _buildOverlayEntry(); + Overlay.of(context).insert(_overlayEntry!); + } + + if (_focusNode.hasFocus == false && _overlayEntry != null) { + _overlayEntry?.remove(); + } + + if (mounted) { + setState(() { + _selectionMode = _focusNode.hasFocus == true; + }); + } + + if (_controller != null) { + _controller!.value._isDropdownOpen = _focusNode.hasFocus == true; + } + } + + /// Handles the widget rebuild when the options are changed externally. + @override + void didUpdateWidget(covariant TreeSelectDropDown oldWidget) { + if (widget.controller == null && oldWidget.controller != null) { + _controller = TreeSelectController(); + } else if (widget.controller != null && oldWidget.controller == null) { + _controller!.dispose(); + _controller = null; + } + + /// If the options are changed externally, then the options are updated. + if (listEquals(widget.options, oldWidget.options) == false) { + _options.clear(); + _options.addAll(widget.options); + + /// If the controller is not null, then the options are updated in the controller. + if (_controller != null) { + _controller!.setOptions(_options); + } + } + + /// If the selected options are changed externally, then the selected options are updated. + if (listEquals(widget.selectedOptions, oldWidget.selectedOptions) == + false) { + _selectedOptions.clear(); + _selectedOptions.addAll(widget.options + .where((element) => widget.selectedOptions.contains(element))); + + /// If the controller is not null, then the selected options are updated in the controller. + if (_controller != null) { + _controller!.setSelectedOptions(_selectedOptions); + } + } + super.didUpdateWidget(oldWidget); + } + + /// Calculate offset size for dropdown. + List _calculateOffsetSize() { + RenderBox? renderBox = context.findRenderObject() as RenderBox?; + + var size = renderBox?.size ?? Size.zero; + var offset = renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + + final availableHeight = MediaQuery.of(context).size.height - offset.dy; + + return [size, availableHeight]; + } + + @override + Widget build(BuildContext context) { + ///calculate the dropdown width based on the view + double dropdownWidth = + AppView.isMobileView(MediaQuery.of(context).size.width) + ? Default.mobileInputWidth + : Default.desktopInputWidth; + return Column( + children: [ + CompositedTransformTarget( + link: _layerLink, + child: Focus( + canRequestFocus: true, + skipTraversal: true, + focusNode: _focusNode, + child: InkWell( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + hoverColor: Colors.transparent, + onTap: _toggleFocus, + child: StatefulBuilder(builder: (context, setState) { + return Container( + height: Default.height, + width: dropdownWidth, + constraints: const BoxConstraints( + minWidth: Default.mobileInputWidth, + minHeight: Default.height, + ), + padding: const EdgeInsets.symmetric( + horizontal: kPadding, + ), + decoration: _getContainerDecoration(), + child: Row( + children: [ + Expanded( + child: (_selectedOptions.isNotEmpty) + ? (widget.treeSelectionType == + TreeSelectionType.MultiSelect) + ? Text('${_selectedOptions.length} Selected') + : Text(_selectedOptions.first.code.toString()) + : const SizedBox(), + ), + AnimatedRotation( + turns: _selectionMode ? 0.5 : 0, + duration: DropdownConstants.animationDuration, + child: widget.suffixIcon, + ), + ], + ), + ); + }), + ), + ), + ), + const Gap( + kPadding, + ), + if (_anyItemSelected && + widget.treeSelectionType == TreeSelectionType.MultiSelect) + SizedBox( + width: dropdownWidth, + child: _getContainerContent(), + ) + ], + ); + } + + /// Container Content for the dropdown. + Widget _getContainerContent() { + return _buildSelectedItems(); + } + + /// Build the selected items for the dropdown. + Widget _buildSelectedItems() { + return Wrap( + spacing: widget.chipConfig.spacing, + runSpacing: widget.chipConfig.runSpacing, + children: [ + ..._selectedOptions.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + + Widget chip = _buildChip(item, widget.chipConfig); + + return chip; + }), + if (_selectedOptions.isNotEmpty) + + /// Display "Clear All" only if there are selected options + InkWell( + onTap: () => clear(), + + /// onTap Clear all the + child: Chip( + backgroundColor: const DigitColors().white, + shape: RoundedRectangleBorder( + side: BorderSide( + color: const DigitColors().burningOrange, + ), + borderRadius: BorderRadius.circular(50), + ), + padding: const EdgeInsets.symmetric( + horizontal: kPadding, + ), + labelPadding: const EdgeInsets.symmetric(vertical: kPadding / 4), + label: Text('Clear All', + style: TextStyle(color: const DigitColors().burningOrange)), + ), + ), + ], + ); + } + + /// Build the selected item chip. + Widget _buildChip(TreeNode item, ChipConfig chipConfig) { + return SelectionChip( + item: item, + chipConfig: chipConfig, + onItemDelete: (removedItem) { + if (_controller != null) { + _controller!.clearSelection(removedItem); + } else { + setState(() { + _selectedOptions.remove(removedItem); + }); + + widget.onOptionSelected?.call(_selectedOptions); + } + if (_focusNode.hasFocus) _focusNode.unfocus(); + }, + ); + } + + /// return true if any item is selected. + bool get _anyItemSelected => _selectedOptions.isNotEmpty; + + /// Container decoration for the dropdown. + Decoration _getContainerDecoration() { + return BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.zero, + border: _selectionMode + ? Border.all( + color: const DigitColors().burningOrange, + width: 1, + ) + : Border.all( + color: const DigitColors().davyGray, + width: 1, + ), + ); + } + + /// Dispose the focus node and overlay entry. + @override + void dispose() { + if (_overlayEntry?.mounted == true) { + if (_overlayState != null && _overlayEntry != null) { + _overlayEntry?.remove(); + } + _overlayEntry = null; + _overlayState?.dispose(); + } + _focusNode.removeListener(_handleFocusChange); + _focusNode.dispose(); + _controller?.removeListener(_handleControllerChange); + + if (widget.controller == null || widget.controller?.isDisposed == true) { + _controller!.dispose(); + } + + super.dispose(); + } + + /// Handle the focus change on tap outside of the dropdown. + void _onOutSideTap() { + _focusNode.unfocus(); + } + + /// Method to toggle the focus of the dropdown. + void _toggleFocus() { + if (_focusNode.hasFocus) { + _focusNode.unfocus(); + } else { + _focusNode.requestFocus(); + } + } + + /// Create the overlay entry for the dropdown. + OverlayEntry _buildOverlayEntry() { + /// Calculate the offset and the size of the dropdown button + final values = _calculateOffsetSize(); + + /// Get the size from the first item in the values list + final size = values[0] as Size; + + return OverlayEntry(builder: (context) { + List options = widget.options; + List selectedOptions = [..._selectedOptions]; + + return StatefulBuilder(builder: ((context, dropdownState) { + return Stack( + children: [ + Positioned.fill( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: _onOutSideTap, + child: Container( + color: Colors.transparent, + ), + ), + ), + CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + targetAnchor: Alignment.bottomLeft, + followerAnchor: Alignment.topLeft, + offset: Offset.zero, + child: Material( + borderRadius: BorderRadius.zero, + shadowColor: null, + clipBehavior: Clip.none, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: SizedBox( + width: size.width, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildFlatOptions( + values, options, selectedOptions, dropdownState), + ], + ), + ), + ), + ), + ), + ], + ); + })); + }); + } + + Widget _buildFlatOptions(List values, List options, + List selectedOptions, StateSetter dropdownState) { + return ConstrainedBox( + constraints: BoxConstraints( + maxHeight: values[1] - 30, + ), + child: ListView.separated( + separatorBuilder: (_, __) => const SizedBox(height: 0), + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: options.length, + itemBuilder: (context, index) { + final option = options[index]; + bool isSelected = + selectedOptions.any((item) => item.name == option.name); + Color backgroundColor = index % 2 == 0 + ? const DigitColors().white + : const DigitColors().alabasterWhite; + return _buildOption( + option, + isSelected, + dropdownState, + backgroundColor, + selectedOptions, + ); + }, + ), + ); + } + + Widget _buildOption( + TreeNode option, + bool isSelected, + StateSetter dropdownState, + Color backgroundColor, + List selectedOptions, + ) { + return TreeNodeWidget( + currentOption: option, + option: option, + isSelected: isSelected, + selectedOptions: selectedOptions, + backgroundColor: backgroundColor, + focusNode: _focusNode, + treeSelectionType: widget.treeSelectionType, + onOptionSelected: (updatedOptions) { + if (widget.treeSelectionType == TreeSelectionType.MultiSelect) { + dropdownState(() { + selectedOptions.clear(); + selectedOptions.addAll(updatedOptions); + }); + setState(() { + _selectedOptions.clear(); + _selectedOptions.addAll(updatedOptions); + }); + } else { + dropdownState(() { + selectedOptions.clear(); + selectedOptions.addAll(updatedOptions); + }); + setState(() { + _selectedOptions.clear(); + _selectedOptions.addAll(updatedOptions); + }); + } + + if (_controller != null) { + _controller!.value._selectedOptions.clear(); + _controller!.value._selectedOptions.addAll(_selectedOptions); + } + + widget.onOptionSelected?.call(selectedOptions); + }, + ); + } + + /// Clear the selected options. + /// [TreeSelectController] is used to clear the selected options. + void clear() { + if (_controller != null && !_controller!._isDisposed) { + _controller!.clearAllSelection(); + } else { + setState(() { + _selectedOptions.clear(); + }); + widget.onOptionSelected?.call(_selectedOptions); + } + if (_focusNode.hasFocus) _focusNode.unfocus(); + } + + /// handle the controller change. + void _handleControllerChange() { + /// if the controller is null, return. + if (_controller == null || _controller?.isDisposed == true) return; + + /// if current options are not equal to the controller's options, update the state. + if (_options != _controller!.value._options) { + setState(() { + _options.clear(); + _options.addAll(_controller!.value._options); + }); + } + + /// if current selected options are not equal to the controller's selected options, update the state. + if (_selectedOptions != _controller!.value._selectedOptions) { + setState(() { + _selectedOptions.clear(); + _selectedOptions.addAll(_controller!.value._selectedOptions); + }); + widget.onOptionSelected?.call(_selectedOptions); + } + + if (_selectionMode != _controller!.value._isDropdownOpen) { + if (_controller!.value._isDropdownOpen) { + _focusNode.requestFocus(); + } else { + _focusNode.unfocus(); + } + } + } +} + +/// TreeSelect Controller class. +/// This class is used to control the state of the TreeSelectDropdown widget. +/// This is just base class. The implementation of this class is in the TreeSelectController class. +/// The implementation of this class is hidden from the user. +class _TreeSelectController { + final List _options = []; + final List _selectedOptions = []; + bool _isDropdownOpen = false; +} + +/// implementation of the treeSelectController class. +class TreeSelectController extends ValueNotifier<_TreeSelectController> { + TreeSelectController() : super(_TreeSelectController()); + + bool _isDisposed = false; + + bool get isDisposed => _isDisposed; + + /// set the dispose method. + @override + void dispose() { + super.dispose(); + _isDisposed = true; + } + + /// Clear the selected options. + /// [TreeSelectController] is used to clear the selected options. + void clearAllSelection() { + value._selectedOptions.clear(); + notifyListeners(); + } + + /// clear specific selected option + /// [TreeSelectController] is used to clear specific selected option. + void clearSelection(TreeNode option) { + if (!value._selectedOptions + .any((item) => item.code == option.code && item.name == option.name)) { + return; + } + value._selectedOptions.removeWhere( + (item) => item.code == option.code && item.name == option.name); + notifyListeners(); + } + + /// select the options + /// [TreeSelectController] is used to select the options. + void setSelectedOptions(List options) { + if (options.any((element) => !value._options.contains(element))) { + throw Exception('Cannot select options that are not in the options list'); + } + + value._selectedOptions.clear(); + value._selectedOptions.addAll(options); + notifyListeners(); + } + + /// add selected option + /// [TreeSelectController] is used to add selected option. + void addSelectedOption(TreeNode option) { + if (!value._options.contains(option)) { + throw Exception('Cannot select option that is not in the options list'); + } + + value._selectedOptions.add(option); + notifyListeners(); + } + + /// set options + /// [TreeSelectController] is used to set options. + void setOptions(List options) { + value._options.clear(); + value._options.addAll(options); + notifyListeners(); + } + + /// get options + List get options => value._options; + + /// get selected options + List get selectedOptions => value._selectedOptions; + + /// get is dropdown open + bool get isDropdownOpen => value._isDropdownOpen; + + /// show dropdown + /// [TreeSelectController] is used to show dropdown. + void showDropdown() { + if (value._isDropdownOpen) return; + value._isDropdownOpen = true; + notifyListeners(); + } + + /// hide dropdown + /// [TreeSelectController] is used to hide dropdown. + void hideDropdown() { + if (!value._isDropdownOpen) return; + value._isDropdownOpen = false; + notifyListeners(); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/helper_widget/selection_chip.dart b/flutter/digit-ui-components/digit_components/lib/widgets/helper_widget/selection_chip.dart new file mode 100644 index 00000000000..6cdaedde2f8 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/helper_widget/selection_chip.dart @@ -0,0 +1,43 @@ +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; +import '../../constants/app_constants.dart'; +import '../../enum/app_enums.dart'; +import '../../models/chipModel.dart'; + +class SelectionChip extends StatelessWidget { + final SelectionType? selectionType; + final ChipConfig chipConfig; + final Function(dynamic) onItemDelete; + final dynamic item; + + const SelectionChip({ + Key? key, + required this.chipConfig, + required this.item, + required this.onItemDelete, + this.selectionType, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Chip( + padding: chipConfig.padding, + label: selectionType == SelectionType.nestedMultiSelect + ? Text('${item.type}: ${item.name}') + : Text(item.name), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(Default.defaultChipRadius), + ), + deleteIcon: Icon( + Icons.cancel, + color: const DigitColors().davyGray, + ), + deleteIconColor: chipConfig.deleteIconColor, + labelPadding: const EdgeInsets.symmetric(vertical: kPadding / 2), + backgroundColor: const DigitColors().quillGray, + labelStyle: chipConfig.labelStyle ?? + DigitTheme.instance.mobileTheme.textTheme.bodyMedium, + onDeleted: () => onItemDelete(item), + ); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/helper_widget/tree_node_widget.dart b/flutter/digit-ui-components/digit_components/lib/widgets/helper_widget/tree_node_widget.dart new file mode 100644 index 00000000000..b1822473ab5 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/helper_widget/tree_node_widget.dart @@ -0,0 +1,215 @@ +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; +import '../../enum/app_enums.dart'; +import '../../models/TreeModel.dart'; +import '../atoms/digit_checkbox_icon.dart'; + +class TreeNodeWidget extends StatefulWidget { + final TreeNode option; + final TreeNode currentOption; + final bool isSelected; + final List selectedOptions; + final Color backgroundColor; + final ValueChanged> onOptionSelected; + final FocusNode focusNode; + final TreeSelectionType treeSelectionType; + + const TreeNodeWidget({ + Key? key, + required this.option, + required this.currentOption, + required this.isSelected, + required this.selectedOptions, + required this.onOptionSelected, + required this.backgroundColor, + required this.focusNode, + required this.treeSelectionType, + }) : super(key: key); + + @override + _TreeNodeWidgetState createState() => _TreeNodeWidgetState(); +} + +class _TreeNodeWidgetState extends State { + bool _isExpanded = false; + bool _isSelected = false; + + /// Update _areAllChildrenSelected method in _TreeNodeWidgetState + bool _areAllChildrenSelected(TreeNode node) { + if (node.children.isEmpty) { + return widget.selectedOptions.any((item) => item.code == node.code); + } + + return node.children.every((child) => _areAllChildrenSelected(child)); + } + + /// Update _isAnyChildSelected method in _TreeNodeWidgetState + bool _isAnyChildSelected(TreeNode node) { + return node.children.any((child) => + widget.selectedOptions.any((item) => item.code == child.code) || + _isAnyChildSelected(child)); + } + + /// Update _selectAllChildren method in _TreeNodeWidgetState + void _selectAllChildren(TreeNode node) { + if (node.children.isEmpty) { + if (!widget.selectedOptions.any((item) => item.code == node.code)) { + widget.onOptionSelected([...widget.selectedOptions, node]); + } + } else { + for (final child in node.children) { + _selectAllChildren(child); + } + } + } + + /// Update _deselectAllChildren method in _TreeNodeWidgetState + void _deselectAllChildren(TreeNode node) { + if (node.children.isEmpty) { + widget.onOptionSelected([...widget.selectedOptions] + ..removeWhere((item) => item.code == node.code)); + } else { + for (final child in node.children) { + _deselectAllChildren(child); + } + } + } + + bool _selected(TreeNode node) { + return widget.selectedOptions.any((item) => item.code == node.code); + } + + @override + Widget build(BuildContext context) { + return ListTile( + selectedColor: Colors.transparent, + selected: _selected(widget.currentOption), + autofocus: false, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + splashColor: Colors.transparent, + tileColor: widget.backgroundColor, + textColor: const DigitColors().davyGray, + selectedTileColor: + widget.treeSelectionType == TreeSelectionType.singleSelect + ? const DigitColors().burningOrange + : const DigitColors().white, + contentPadding: _isExpanded + ? const EdgeInsets.only(top: 4, bottom: 4) + : const EdgeInsets.symmetric(horizontal: 0, vertical: 0), + title: Column( + children: [ + Row( + children: [ + Transform.rotate( + angle: _isExpanded ? 0 : -1.5, + child: Icon( + Icons.arrow_drop_down, + size: 24, + color: const DigitColors().woodsmokeBlack, + ), + ), + if (widget.treeSelectionType == TreeSelectionType.MultiSelect) + InkWell( + onTap: () { + _isSelected = _areAllChildrenSelected(widget.currentOption); + if (_isSelected) { + _deselectAllChildren(widget.currentOption); + } else { + _selectAllChildren(widget.currentOption); + } + setState(() { + _isSelected = + _areAllChildrenSelected(widget.currentOption); + }); + }, + child: _areAllChildrenSelected(widget.currentOption) + ? const DigitCheckboxIcon( + state: CheckboxState.checked, + ) + : _isAnyChildSelected(widget.currentOption) + ? const DigitCheckboxIcon( + state: CheckboxState.intermediate) + : const DigitCheckboxIcon( + state: CheckboxState.unchecked), + ), + const SizedBox( + width: 4, + ), + Text( + widget.currentOption.name, + style: DigitTheme.instance.mobileTheme.textTheme.headlineSmall + ?.copyWith( + color: const DigitColors().davyGray, + ), + ), + ], + ), + if (_isExpanded && widget.currentOption.children.isNotEmpty) + Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + border: Border( + left: BorderSide( + color: const DigitColors().quillGray, + width: 2.0, // specify the width of the border + ), + top: BorderSide.none, + bottom: BorderSide.none, + right: BorderSide.none, + ), + ), + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column( + children: widget.currentOption.children.map((child) { + bool isChildSelected = + _areAllChildrenSelected(widget.currentOption); + return TreeNodeWidget( + option: widget.option, + isSelected: isChildSelected, + focusNode: widget.focusNode, + selectedOptions: widget.selectedOptions, + onOptionSelected: widget.onOptionSelected, + backgroundColor: widget.backgroundColor, + treeSelectionType: widget.treeSelectionType, + currentOption: child, + ); + }).toList(), + ), + ), + ), + ), + ], + ), + onTap: () { + if (widget.currentOption.children.isNotEmpty) { + setState(() { + _isExpanded = !_isExpanded; + }); + } else if (widget.selectedOptions + .any((item) => item.code == widget.currentOption.code)) { + widget.onOptionSelected([...widget.selectedOptions] + ..removeWhere((item) => item.code == widget.currentOption.code)); + } else { + if (widget.treeSelectionType == TreeSelectionType.MultiSelect) { + if (!widget.selectedOptions + .any((item) => item.code == widget.currentOption.code)) { + widget.onOptionSelected( + [...widget.selectedOptions, widget.currentOption]); + } + } else { + setState(() { + widget.selectedOptions.clear(); + }); + widget.onOptionSelected( + [...widget.selectedOptions, widget.currentOption]); + widget.focusNode.unfocus(); + } + } + }, + ); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/powered_by_digit.dart b/flutter/digit-ui-components/digit_components/lib/widgets/powered_by_digit.dart new file mode 100644 index 00000000000..79caa655e5b --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/powered_by_digit.dart @@ -0,0 +1,41 @@ +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; + +class PoweredByDigit extends StatefulWidget { + final Size? size; + final EdgeInsets? padding; + final String version; + final bool isWhiteLogo; + + const PoweredByDigit( + {super.key, + this.size, + this.padding, + this.isWhiteLogo = false, + required this.version}); + + @override + State createState() => _PoweredByDigitState(); +} + +class _PoweredByDigitState extends State { + @override + Widget build(BuildContext context) => SafeArea( + child: Center( + child: Column(children: [ + Container( + height: 24, + padding: widget.padding ?? const EdgeInsets.all(4), + alignment: Alignment.center, + child: Image.asset('assets/images/powered_by_digit.png', + package: 'digit_components', + fit: BoxFit.contain, + color: widget.isWhiteLogo + ? DigitTheme.instance.colorScheme.onPrimary + : null), + ), + Text(widget.version) + ]), + ), + ); +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/scrollable_content.dart b/flutter/digit-ui-components/digit_components/lib/widgets/scrollable_content.dart new file mode 100644 index 00000000000..3a816101516 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/scrollable_content.dart @@ -0,0 +1,57 @@ +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; + +class ScrollableContent extends StatelessWidget { + final Widget? header; + final Widget? footer; + final List slivers; + final bool? primary; + final ScrollController? controller; + final MainAxisAlignment mainAxisAlignment; + final CrossAxisAlignment crossAxisAlignment; + final List children; + + const ScrollableContent({ + super.key, + this.footer, + this.header, + this.primary, + this.controller, + this.mainAxisAlignment = MainAxisAlignment.start, + this.crossAxisAlignment = CrossAxisAlignment.start, + this.children = const [], + this.slivers = const [], + }); + + @override + Widget build(BuildContext context) { + return CustomScrollView( + controller: controller, + primary: primary, + slivers: [ + if (header != null) SliverToBoxAdapter(child: header), + ...slivers, + SliverFillRemaining( + hasScrollBody: false, + child: Center( + child: Column( + crossAxisAlignment: crossAxisAlignment, + children: [ + Expanded( + child: Column( + mainAxisAlignment: mainAxisAlignment, + children: children, + ), + ), + if (footer != null) ...[ + const SizedBox(height: kPadding*2), + footer!, + ], + ], + ), + ), + ), + ], + ); + } +} diff --git a/flutter/digit-ui-components/digit_components/lib/widgets/widgets.dart b/flutter/digit-ui-components/digit_components/lib/widgets/widgets.dart new file mode 100644 index 00000000000..a1af12ff1e0 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/lib/widgets/widgets.dart @@ -0,0 +1,10 @@ + +export './powered_by_digit.dart'; +export './scrollable_content.dart'; + + + + + + + diff --git a/flutter/digit-ui-components/digit_components/pubspec.yaml b/flutter/digit-ui-components/digit_components/pubspec.yaml new file mode 100644 index 00000000000..53c9017fc58 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/pubspec.yaml @@ -0,0 +1,55 @@ +name: digit_components +description: Digit UI Design specification +version: 0.0.1+8 +homepage: + +environment: + sdk: '>=2.19.0 <3.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + flutter_bloc: ^8.1.1 + freezed_annotation: ^2.2.0 + google_fonts: ^4.0.4 + horizontal_data_table: ^4.1.3 + location: ^5.0.0 + location_platform_interface: ^3.0.0 + json_annotation: ^4.7.0 + logging: ^1.1.0 + build_runner: ^2.2.1 + reactive_forms: ^14.1.0 + plugin_platform_interface: ^2.1.3 + flutter_localizations: + sdk: flutter + fluttertoast: ^8.1.2 + flutter_spinkit: ^5.1.0 + reactive_flutter_typeahead: ^0.8.1 + flutter_typeahead: ^4.3.7 + overlay_builder: ^1.1.0 + package_info_plus: ^4.1.0 + easy_stepper: ^0.5.2+1 + intl: ^0.18.0 + geolocator: ^10.1.0 + collection: ^1.15.0 + gap: ^2.0.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + json_serializable: ^6.4.0 + freezed: ^2.2.0 + bloc_test: ^9.1.0 + mocktail: ^0.3.0 + mockito: ^5.0.15 + +flutter: + assets: + - assets/images/ + fonts: + - family: Roboto + fonts: + - asset: assets/fonts/Roboto/Roboto-Regular.ttf + diff --git a/flutter/digit-ui-components/digit_components/test/theme/theme_test.dart b/flutter/digit-ui-components/digit_components/test/theme/theme_test.dart new file mode 100644 index 00000000000..3dd8c2f48d0 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/theme/theme_test.dart @@ -0,0 +1,144 @@ +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + DigitUi.instance.initThemeComponents(); + + group('Digit theme', () { + group('Typography', () { + final digitTheme = DigitTheme.instance; + + final widget = MaterialApp( + theme: digitTheme.mobileTheme, + home: Scaffold( + body: Builder(builder: (context) { + final theme = Theme.of(context).textTheme; + return Column( + children: [ + Text('Headline XL', style: theme.displayMedium), + Text('Headline L', style: theme.headlineLarge), + Text('Headline M', style: theme.headlineMedium), + Text('Headline S', style: theme.headlineSmall), + Text('Caption XL', style: theme.labelLarge), + Text('Caption L', style: theme.labelMedium), + Text('Caption M', style: theme.labelSmall), + Text('Body L', style: theme.bodyLarge), + Text('Body S', style: theme.bodyMedium), + Text('Body XS', style: theme.bodySmall), + Text('Label', style: digitTheme.mobileTypography.label), + Text('Link', style: digitTheme.mobileTypography.link), + ], + ); + }), + ), + ); + + const normal = Color(0xFF0B0C0C); + const light = Color(0xFF505A5F); + + const designSpecifications = [ + _DesignSpecification( + title: 'Headline XL', + fontSize: 32, + color: normal, + fontWeight: FontWeight.w700, + ), + _DesignSpecification( + title: 'Headline L', + fontSize: 24, + color: normal, + fontWeight: FontWeight.w700, + ), + _DesignSpecification( + title: 'Headline M', + fontSize: 18, + color: normal, + fontWeight: FontWeight.w700, + ), + _DesignSpecification( + title: 'Body L', + fontSize: 16, + color: normal, + fontWeight: FontWeight.w400, + ), + _DesignSpecification( + title: 'Body S', + fontSize: 14, + color: normal, + fontWeight: FontWeight.w400, + ), + _DesignSpecification( + title: 'Caption XL', + fontSize: 19, + color: normal, + fontWeight: FontWeight.w500, + ), + _DesignSpecification( + title: 'Caption L', + fontSize: 18, + color: light, + fontWeight: FontWeight.w400, + ), + _DesignSpecification( + title: 'Link', + fontSize: 16, + color: normal, + fontWeight: FontWeight.w400, + ), + _DesignSpecification( + title: 'Label', + fontSize: 16, + color: normal, + fontWeight: FontWeight.w400, + ), + ]; + + for (final element in designSpecifications) { + group(element.title, () { + testWidgets('matches title', (tester) async { + await tester.pumpWidget(widget); + final child = find.text(element.title); + expect(child, findsOneWidget); + }); + + testWidgets('matches font size', (tester) async { + await tester.pumpWidget(widget); + final child = find.text(element.title); + final textWidget = tester.firstWidget(child) as Text; + expect(textWidget.style?.fontSize, element.fontSize); + }); + + testWidgets('matches font weight', (tester) async { + await tester.pumpWidget(widget); + final child = find.text(element.title); + final textWidget = tester.firstWidget(child) as Text; + expect(textWidget.style?.fontWeight, element.fontWeight); + }); + + testWidgets('matches font color', (tester) async { + await tester.pumpWidget(widget); + final child = find.text(element.title); + final textWidget = tester.firstWidget(child) as Text; + expect(textWidget.style?.color, element.color); + }); + }); + } + }); + }); +} + +class _DesignSpecification { + final String title; + final double fontSize; + final FontWeight fontWeight; + final Color color; + + const _DesignSpecification({ + required this.title, + required this.fontSize, + required this.color, + required this.fontWeight, + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_button_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_button_test.dart new file mode 100644 index 00000000000..5ab6be0f558 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_button_test.dart @@ -0,0 +1,87 @@ +import 'package:digit_components/enum/app_enums.dart'; +import 'package:digit_components/widgets/atoms/digit_button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('DigitButton renders correctly and handles tap', (WidgetTester tester) async { + // Primary button + await _testButton(tester, ButtonType.primary); + + // Secondary button + await _testButton(tester, ButtonType.secondary); + + // Tertiary button + await _testButton(tester, ButtonType.tertiary); + + // Link button + await _testButton(tester, ButtonType.link); + }); + + testWidgets('DigitButton is disabled and handles tap', (WidgetTester tester) async { + // Primary button disabled + await _testDisabledButton(tester, ButtonType.primary); + + // Secondary button disabled + await _testDisabledButton(tester, ButtonType.secondary); + + // Tertiary button disabled + await _testDisabledButton(tester, ButtonType.tertiary); + + // Link button disabled + await _testDisabledButton(tester, ButtonType.link); + }); +} + +Future _testButton(WidgetTester tester, ButtonType buttonType) async { + bool onPressedCalled = false; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitButton( + label: 'Click me', + onPressed: () { + onPressedCalled = true; + }, + type: buttonType, + ), + ), + ), + ); + + expect(find.text('Click me'), findsOneWidget); + expect(tester.widget(find.byType(DigitButton)).isDisabled, false); + + await tester.tap(find.byType(DigitButton)); + await tester.pump(); + + expect(onPressedCalled, true); +} + +Future _testDisabledButton(WidgetTester tester, ButtonType buttonType) async { + bool onPressedCalled = false; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitButton( + label: 'Click me', + onPressed: () { + onPressedCalled = true; + }, + type: buttonType, + isDisabled: true, + ), + ), + ), + ); + + expect(find.text('Click me'), findsOneWidget); + expect(tester.widget(find.byType(DigitButton)).isDisabled, true); + + await tester.tap(find.byType(DigitButton)); + await tester.pump(); + + expect(onPressedCalled, false); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_checkbox_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_checkbox_test.dart new file mode 100644 index 00000000000..1850940878e --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_checkbox_test.dart @@ -0,0 +1,159 @@ +import 'package:digit_components/widgets/atoms/digit_checkbox.dart'; +import 'package:digit_components/widgets/atoms/digit_checkbox_icon.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + + +void main() { + group('DigitCheckbox Widget Tests', () { + testWidgets('DigitCheckbox should render with initial state', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitCheckbox( + label: 'Test Checkbox', + onChanged: (value) {}, + ), + ), + ), + ); + + expect(find.byType(DigitCheckbox), findsOneWidget); + expect(find.byType(DigitCheckboxIcon), findsOneWidget); + expect(find.byIcon(Icons.check), findsNothing); + }); + + testWidgets('DigitCheckbox should change state on tap', (WidgetTester tester) async { + bool isChecked = false; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitCheckbox( + label: 'Test Checkbox', + value: isChecked, + onChanged: (value) { + isChecked = value ?? false; + }, + ), + ), + ), + ); + + expect(find.byType(DigitCheckboxIcon), findsOneWidget); + expect(find.byIcon(Icons.check), findsNothing); + + await tester.tap(find.byType(InkWell)); + await tester.pump(); + + expect(find.byType(DigitCheckboxIcon), findsOneWidget); + expect(find.byIcon(Icons.check), findsOneWidget); + expect(isChecked, true); + }); + + testWidgets('DigitCheckbox should be disabled and not change state on tap', (WidgetTester tester) async { + bool isChecked = false; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitCheckbox( + label: 'Test Checkbox', + value: isChecked, + onChanged: (value) { + isChecked = value ?? false; + }, + disabled: true, + ), + ), + ), + ); + + expect(find.byType(DigitCheckboxIcon), findsOneWidget); + expect(find.byIcon(Icons.check), findsNothing); + + await tester.tap(find.byType(InkWell)); + await tester.pump(); + + expect(find.byType(DigitCheckboxIcon), findsOneWidget); + expect(find.byIcon(Icons.check), findsNothing); + expect(isChecked, false); + }); + + testWidgets('DigitCheckbox should change state on multiple taps', (WidgetTester tester) async { + bool isChecked = false; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitCheckbox( + label: 'Test Checkbox', + value: isChecked, + onChanged: (value) { + isChecked = value ?? false; + }, + ), + ), + ), + ); + + expect(find.byIcon(Icons.check), findsNothing); + + await tester.tap(find.byType(InkWell)); + await tester.pump(); + + expect(find.byIcon(Icons.check), findsOneWidget); + expect(isChecked, true); + + await tester.tap(find.byType(InkWell)); + await tester.pump(); + + expect(find.byIcon(Icons.check), findsNothing); + expect(isChecked, false); + }); + + testWidgets('DigitCheckbox should have a custom icon color', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitCheckbox( + label: 'Test Checkbox', + iconColor: Colors.red, + onChanged: (value) {}, + ), + ), + ), + ); + + final iconWidget = tester.widget(find.byType(DigitCheckboxIcon)); + expect(iconWidget.color, equals(Colors.red)); + }); + + testWidgets('DigitCheckbox should trigger onChanged with correct value', (WidgetTester tester) async { + bool isChecked = false; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitCheckbox( + label: 'Test Checkbox', + value: isChecked, + onChanged: (value) { + isChecked = value ?? false; + }, + ), + ), + ), + ); + + expect(find.byIcon(Icons.check), findsNothing); + + await tester.tap(find.byType(InkWell)); + await tester.pump(); + + expect(find.byIcon(Icons.check), findsOneWidget); + expect(isChecked, true); + }); + + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_date_form_input_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_date_form_input_test.dart new file mode 100644 index 00000000000..b35c2b39bb5 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_date_form_input_test.dart @@ -0,0 +1,160 @@ +import 'package:digit_components/utils/validators/validator.dart'; +import 'package:digit_components/widgets/atoms/digit_date_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_text_form_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/intl.dart'; + +void main() { + group('DigitTextFormInput Tests', () { + late TextEditingController controller; + + setUp(() { + controller = TextEditingController(); + }); + + late FocusNode myFocusNode; + + // testing rendering the widget + + testWidgets('Initial rendering', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDateFormInput(controller: controller), + ), + ), + ); + + // Ensure the widget is rendered + expect(find.byType(DigitDateFormInput), findsOneWidget); + }); + + testWidgets('Date selection updates controller text', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDateFormInput(controller: controller), + ), + ), + ); + + // Tap the suffix icon to show the date picker + await tester.tap(find.byIcon(Icons.date_range)); + await tester.pumpAndSettle(); + + // Select a date from the date picker + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Ensure the controller's text is updated with the selected date + final selectedDate = DateFormat('dd/MM/yyyy').format(DateTime.now()); + expect(controller.text, equals(selectedDate)); + }); + + testWidgets('ReadOnly widget cannot be edited', (WidgetTester tester) async { + final readOnlyController = TextEditingController(text: '2023-12-15'); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDateFormInput(controller: readOnlyController, readOnly: true), + ), + ), + ); + + // Try to tap on the suffix icon (date picker) + await tester.tap(find.byIcon(Icons.date_range)); + await tester.pumpAndSettle(); + + // Ensure the controller's text is not changed + expect(readOnlyController.text, equals('2023-12-15')); + }); + + // TODO + // testWidgets('Validation error is displayed', (WidgetTester tester) async { + // final validationController = TextEditingController(); + // await tester.pumpWidget( + // MaterialApp( + // home: Scaffold( + // body: DigitDateFormInput( + // controller: validationController, + // validations: [Validator(ValidatorType.date, 'Invalid date format')], + // ), + // ), + // ), + // ); + // + // // Enter an invalid date format + // await tester.enterText(find.byType(TextFormField), 'invalid_date'); + // await tester.pumpAndSettle(); + // + // // Ensure the validation error is displayed + // expect(find.text('Invalid date format'), findsOneWidget); + // }); + + testWidgets('Help text is displayed', (WidgetTester tester) async { + final helpTextController = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDateFormInput( + controller: helpTextController, + helpText: 'hint.', + ), + ), + ), + ); + + // Ensure the help text is displayed + expect(find.text('hint.'), findsOneWidget); + }); + + testWidgets('Empty initial value does not trigger validation error', (WidgetTester tester) async { + final emptyController = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDateFormInput( + controller: emptyController, + validations: [Validator(ValidatorType.required, 'Field is required')], + ), + ), + ), + ); + + // Trigger validation without entering anything + await tester.pumpAndSettle(); + + // Ensure no validation error is displayed + expect(find.text('Field is required'), findsNothing); + }); + + testWidgets('Handles tooltip behavior', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDateFormInput( + controller: controller, + label: 'Test Label', + info: true, + infoText: 'Info Tooltip', + ), + ), + ), + ); + + // Verify the tooltip is not initially present + expect(find.text('Info Tooltip'), findsNothing); + + // Trigger the tooltip with a long press (simulate hover) + await tester.longPress(find.byIcon(Icons.info_outline)); + + // Pump the widget tree to allow time for the tooltip to appear + await tester.pump(); + + // Verify the tooltip is now present + expect(find.text('Info Tooltip'), findsOneWidget); + }); + + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_dropdown_input_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_dropdown_input_test.dart new file mode 100644 index 00000000000..71e5ec6afce --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_dropdown_input_test.dart @@ -0,0 +1,253 @@ +import 'package:digit_components/enum/app_enums.dart'; +import 'package:digit_components/models/DropdownModels.dart'; +import 'package:digit_components/widgets/atoms/digit_dropdown_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + + testWidgets('DigitDropdown - Tap suffix icon to open and tap anywhere to close', (WidgetTester tester) async { + /// Build our widget and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDropdown( + textEditingController: TextEditingController(), + onChange: (value, index) {}, + items: const [ + DropdownItem(name: 'Item 1', code: ''), + DropdownItem(name: 'Item 2', code: ''), + DropdownItem(name: 'Item 3', code: ''), + ], + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Dropdown should be open. + expect(find.byType(ListView), findsOneWidget); + + /// Tap anywhere outside the dropdown to close it. + await tester.tap(find.byType(GestureDetector, skipOffstage: false).first); + await tester.pump(); + + /// Dropdown should be closed. + expect(find.byType(ListView), findsNothing); + }); + + testWidgets('DigitDropdown - Select an item', (WidgetTester tester) async { + /// Build our widget and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDropdown( + textEditingController: TextEditingController(), + onChange: (value, index) {}, + items: const [ + DropdownItem(name: 'Item 1', code: '1'), + DropdownItem(name: 'Item 2', code: '2'), + DropdownItem(name: 'Item 3', code: '3'), + ], + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Get the offset of the center of the widget. + final Offset widgetCenter = tester.getCenter(find.text('Item 2')); + + /// Tap at the widget's center. + await tester.tapAt(widgetCenter); + await tester.pump(); + + /// Verify that the selected item is displayed in the text field. + expect(find.text('Item 2'), findsOneWidget); + }); + + testWidgets('DigitDropdown - Filter items', (WidgetTester tester) async { + /// Build our widget and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDropdown( + textEditingController: TextEditingController(), + onChange: (value, index) {}, + items: const [ + DropdownItem(name: 'Apple', code: 'A'), + DropdownItem(name: 'Banana', code: 'B'), + DropdownItem(name: 'Orange', code: 'O'), + ], + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Enter text in the search field to filter items. + await tester.enterText(find.byType(TextField), 'Ban'); + await tester.pump(); + + /// Verify that only 'Banana' is visible in the dropdown. + expect(find.text('Banana'), findsOneWidget); + expect(find.text('Apple'), findsNothing); + expect(find.text('Orange'), findsNothing); + }); + + testWidgets('DigitDropdown - Dropdown with Descriptions', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDropdown( + textEditingController: TextEditingController(), + onChange: (value, index) {}, + items: const [ + DropdownItem(name: 'Item 1', code: '1', description: 'Description 1'), + DropdownItem(name: 'Item 2', code: '2', description: 'Description 2'), + DropdownItem(name: 'Item 3', code: '3', description: 'Description 3'), + ], + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Verify that items with descriptions are displayed. + expect(find.text('Description 1'), findsOneWidget); + expect(find.text('Description 2'), findsOneWidget); + expect(find.text('Description 3'), findsOneWidget); + }); + + + testWidgets('DigitDropdown - Select an item with description', (WidgetTester tester) async { + /// Build our widget and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDropdown( + textEditingController: TextEditingController(), + onChange: (value, index) {}, + items: const [ + DropdownItem(name: 'Item 1', code: '1', description: 'Description 1'), + DropdownItem(name: 'Item 2', code: '2', description: 'Description 2'), + DropdownItem(name: 'Item 3', code: '3', description: 'Description 3'), + ], + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Get the offset of the center of the widget. + final Offset widgetCenter = tester.getCenter(find.text('Item 2')); + + /// Tap at the widget's center. + await tester.tapAt(widgetCenter); + await tester.pump(); + + /// Verify that the selected item with description is displayed in the text field. + expect(find.text('Item 2'), findsOneWidget); + expect(find.text('Description 2'), findsOneWidget); + }); + + + testWidgets('DigitDropdown - Select an item with type', (WidgetTester tester) async { + /// Build our widget and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDropdown( + textEditingController: TextEditingController(), + onChange: (value, index) {}, + dropdownType: DropdownType.nestedSelect, + items: const [ + DropdownItem(name: 'Item 1', code: '1', type: 'Type A'), + DropdownItem(name: 'Item 2', code: '2', type: 'Type B'), + DropdownItem(name: 'Item 3', code: '3', type: 'Type A'), + ], + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pumpAndSettle(); /// Wait for animations to complete. + + /// Get the offset of the center of the widget. + final Offset widgetCenter = tester.getCenter(find.text('Item 2')); + + /// Tap at the widget's center. + await tester.tapAt(widgetCenter); + await tester.pumpAndSettle(); /// Wait for animations to complete. + + /// Verify that the selected item with type is displayed in the text field. + expect(find.byType(DigitDropdown), findsOneWidget); + final String selectedValue = tester.widget(find.byType(DigitDropdown)).textEditingController.text; + expect(selectedValue, 'Type B,Item 2'); + }); + + testWidgets('DigitDropdown - Empty Item List', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDropdown( + textEditingController: TextEditingController(), + onChange: (value, index) {}, + items: const [], // Empty list + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Verify that the empty item text is displayed. + expect(find.text('No Options available'), findsOneWidget); + }); + + testWidgets('DigitDropdown - Dropdown with Icons', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitDropdown( + textEditingController: TextEditingController(), + onChange: (value, index) {}, + textIcon: Icons.star, + items: const [ + DropdownItem(name: 'Item 1', code: '1', ), + DropdownItem(name: 'Item 2', code: '2', ), + DropdownItem(name: 'Item 3', code: '3', ), + ], + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Verify that the items with icons are displayed. + expect(find.byIcon(Icons.star), findsWidgets); + }); + +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_location_form_input_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_location_form_input_test.dart new file mode 100644 index 00000000000..f3a03f1d329 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_location_form_input_test.dart @@ -0,0 +1,106 @@ +import 'package:digit_components/utils/validators/validator.dart'; +import 'package:digit_components/widgets/atoms/digit_location_form_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:mockito/mockito.dart'; + +class MockGeolocator extends Mock implements GeolocatorPlatform {} + +void main() { + group('DigitTextFormInput Tests', () { + late TextEditingController controller; + late MockGeolocator mockGeolocator; + + setUp(() { + controller = TextEditingController(); + mockGeolocator = MockGeolocator(); + }); + + testWidgets('Initial rendering', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitLocationFormInput(controller: controller), + ), + ), + ); + + // Ensure the widget is rendered + expect(find.byType(DigitLocationFormInput), findsOneWidget); + }); + + // TODO + + // testWidgets('Location permission denied does not update controller text', (WidgetTester tester) async { + // when(mockGeolocator.checkPermission()).thenAnswer((_) async => LocationPermission.denied); + // + // await tester.pumpWidget( + // MaterialApp( + // home: Scaffold( + // body: DigitLocationFormInput(controller: controller), + // ), + // ), + // ); + // + // // Tap the suffix icon to get location + // await tester.tap(find.byIcon(Icons.my_location)); + // + // // Allow time for the asynchronous operation to complete + // await tester.pumpAndSettle(); + // print('tttttttttttttttttttttt${controller.text}'); + // + // // Ensure the controller's text is not updated + // expect(controller.text, isEmpty); + // }); + + // TODO + + // testWidgets('Location permission granted updates controller text', (WidgetTester tester) async { + // when(mockGeolocator.checkPermission()).thenAnswer((_) async => LocationPermission.whileInUse); + // when(mockGeolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high)) + // .thenAnswer((_) async => Position(latitude: 40.7128, longitude: -74.0060)); + // + // await tester.pumpWidget( + // MaterialApp( + // home: Scaffold( + // body: DigitLocationFormInput(controller: controller), + // ), + // ), + // ); + // + // // Tap the suffix icon to get location + // await tester.tap(find.byIcon(Icons.my_location)); + // await tester.pumpAndSettle(); + // + // // Ensure the controller's text is updated with the location + // expect(controller.text, equals("40.7128, -74.0060")); + // }); + + //TODO + + // testWidgets('Location permission denied shows error message', (WidgetTester tester) async { + // when(mockGeolocator.checkPermission()).thenAnswer((_) async => LocationPermission.denied); + // + // await tester.pumpWidget( + // MaterialApp( + // home: Scaffold( + // body: DigitLocationFormInput( + // controller: controller, + // validations: [Validator(ValidatorType.required, 'Location is required')], + // ), + // ), + // ), + // ); + // + // // Tap the suffix icon to get location + // await tester.tap(find.byIcon(Icons.my_location)); + // await tester.pumpAndSettle(); + // + // // Ensure the validation error is displayed + // expect(find.text('Location is required'), findsOneWidget); + // }); + + + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_multiselect_dropdown_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_multiselect_dropdown_test.dart new file mode 100644 index 00000000000..ec177affd62 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_multiselect_dropdown_test.dart @@ -0,0 +1,206 @@ +import 'package:digit_components/enum/app_enums.dart'; +import 'package:digit_components/models/DropdownModels.dart'; +import 'package:digit_components/widgets/atoms/digit_multiselect_dropdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets( + 'MultiSelectDropDown - Initial State', (WidgetTester tester) async { + /// Build our widget and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: MultiSelectDropDown( + onOptionSelected: (selectedOptions) {}, + options: const [ + DropdownItem(name: 'Item 1', code: '1'), + DropdownItem(name: 'Item 2', code: '2'), + DropdownItem(name: 'Item 3', code: '3'), + ], + ), + ), + ), + ); + + /// Verify that the dropdown is not initially visible. + expect(find.byType(MultiSelectDropDown), findsOneWidget); + expect(find.byType(ListView), findsNothing); + }); + + testWidgets('MultiSelectDropDown - Selection', (WidgetTester tester) async { + /// Build our widget and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: MultiSelectDropDown( + onOptionSelected: (selectedOptions) {}, + options: const [ + DropdownItem(name: 'Item 1', code: '1'), + DropdownItem(name: 'Item 2', code: '2'), + DropdownItem(name: 'Item 3', code: '3'), + ], + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Get the offset of the center of the widget. + final Offset widgetCenter = tester.getCenter(find.text('Item 2')); + + /// Tap at the widget's center using tester.tapAt. + await tester.tapAt(widgetCenter); + await tester.pumpAndSettle(); + + /// Close the dropdown by tapping outside. + await tester.tap(find.byType(GestureDetector).first); + await tester.pumpAndSettle(); + /// Verify that the selected item is displayed. + + expect(find.text('Item 2'), findsWidgets); + }); + + + testWidgets( + 'MultiSelectDropDown - Clear Selection', (WidgetTester tester) async { + /// Build our widget and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: MultiSelectDropDown( + onOptionSelected: (selectedOptions) {}, + options: const [ + DropdownItem(name: 'Item 1', code: '1'), + DropdownItem(name: 'Item 2', code: '2'), + DropdownItem(name: 'Item 3', code: '3'), + ], + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Get the offset of the center of the widget. + final Offset widgetCenter = tester.getCenter(find.text('Item 2')); + + /// Tap at the widget's center using tester.tapAt. + await tester.tapAt(widgetCenter); + await tester.pumpAndSettle(); + + /// Close the dropdown by tapping outside. + await tester.tap(find.byType(GestureDetector).first); + await tester.pumpAndSettle(); + + /// Verify that the selected item is displayed. + expect(find.text('1 Selected'), findsOneWidget); + expect(find.text('Item 2'), findsOneWidget); + + /// Clear the selection. + await tester.tap(find.text('Clear All')); + await tester.pump(); + + /// Verify that the selection is cleared. + expect(find.text('Item 2'), findsNothing); + }); + + testWidgets( + 'MultiSelectDropDown - Nested MultiSelect', (WidgetTester tester) async { + /// Build our widget and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: MultiSelectDropDown( + onOptionSelected: (selectedOptions) {}, + options: const [ + DropdownItem(name: 'Item 1', code: '1', type: 'Type A'), + DropdownItem(name: 'Item 2', code: '2', type: 'Type A'), + DropdownItem(name: 'Item 3', code: '3', type: 'Type A'), + DropdownItem(name: 'Item 4', code: '4', type: 'Type B'), + DropdownItem(name: 'Item 5', code: '5', type: 'Type B'), + ], + selectionType: SelectionType.nestedMultiSelect, + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Get the offset of the center of the widget. + final Offset widgetCenter = tester.getCenter(find.text('Item 1')); + + /// Tap at the widget's center using tester.tapAt. + await tester.tapAt(widgetCenter); + + + /// Get the offset of the center of the widget. + final Offset widgetCenter1 = tester.getCenter(find.text('Item 4')); + + /// Tap at the widget's center using tester.tapAt. + await tester.tapAt(widgetCenter1); + await tester.pumpAndSettle(); + + /// Close the dropdown by tapping outside. + await tester.tap(find.byType(GestureDetector).first); + await tester.pumpAndSettle(); + + /// Verify that the selected items are displayed. + expect(find.text('2 Selected'), findsOneWidget); + expect(find.text('Type A: Item 1'), findsOneWidget); + expect(find.text('Type B: Item 4'), findsOneWidget); + + }); + + /// TO DO + + // testWidgets('MultiSelectDropDown - Controller Interactions', ( + // WidgetTester tester) async { + // final controller = MultiSelectController(); + // + // // Build our widget and trigger a frame. + // await tester.pumpWidget( + // MaterialApp( + // home: Scaffold( + // body: MultiSelectDropDown( + // onOptionSelected: (selectedOptions) {}, + // options: const [ + // DropdownItem(name: 'Item 1', code: '1'), + // DropdownItem(name: 'Item 2', code: '2'), + // DropdownItem(name: 'Item 3', code: '3'), + // ], + // controller: controller, + // ), + // ), + // ), + // ); + // + // // Verify that the dropdown is not initially visible. + // expect(find.byType(MultiSelectDropDown), findsOneWidget); + // expect(find.byType(ListView), findsNothing); + // + // // Show dropdown using the controller. + // controller.showDropdown(); + // await tester.pumpAndSettle(); + // await Future.delayed(Duration(milliseconds: 2)); + // + // // Verify that the dropdown is visible. + // expect(find.byType(ListView), findsOneWidget); + // + // // // Hide dropdown using the controller. + // // controller.hideDropdown(); + // // await tester.pumpAndSettle(); + // + // // // Verify that the dropdown is hidden. + // // expect(find.byType(ListView), findsNothing); + // }); + +} \ No newline at end of file diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_numeric_form_input_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_numeric_form_input_test.dart new file mode 100644 index 00000000000..92ba8dc56bc --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_numeric_form_input_test.dart @@ -0,0 +1,289 @@ +import 'package:digit_components/utils/validators/validator.dart'; +import 'package:digit_components/widgets/atoms/digit_numeric_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_text_form_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DigitTextFormInput Tests', () { + testWidgets('DigitNumericFormInput increments and decrements value', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: TextEditingController(), + label: 'Numeric Input', + initialValue: '0', + step: 1, + ), + ), + ), + ); + + // Find the widget in the tree + final numericInput = find.byType(DigitNumericFormInput); + + // Verify the initial value + expect((tester.widget(numericInput) as DigitNumericFormInput).controller.text, '0'); + + // Tap on the suffix icon to increment value + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that the value has been incremented + expect((tester.widget(numericInput) as DigitNumericFormInput).controller.text, '1'); + + // Tap on the prefix icon to decrement value + await tester.tap(find.byIcon(Icons.remove)); + await tester.pump(); + + // Verify that the value has been decremented + expect((tester.widget(numericInput) as DigitNumericFormInput).controller.text, '0'); + }); + + testWidgets('Numeric form input validates correctly', (WidgetTester tester) async { + TextEditingController controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: controller, + label: 'Test Label', + validations: [ + Validator(ValidatorType.minValue, 2, + errorMessage: 'Value must be greater than or equal to 1'), + Validator(ValidatorType.maxValue, 10, + errorMessage: 'Minimum value is 10'), + ], + ), + ), + ), + ); + + // Enter a value less than the minimum allowed + await tester.enterText(find.byType(DigitNumericFormInput), '0'); + + FocusScope.of(tester.element(find.byType(DigitNumericFormInput))).unfocus(); + await tester.pump(); + + // Verify that an error is shown + expect(find.text('Value must be greater than or equal to 1'), findsOneWidget); + + // Enter a value within the allowed range + await tester.enterText(find.byType(DigitNumericFormInput), '5'); + FocusScope.of(tester.element(find.byType(DigitNumericFormInput))).unfocus(); + await tester.pump(); + + // Verify that no error is shown + expect(find.text('Value must be greater than or equal to 1'), findsNothing); + }); + + testWidgets('DigitNumericFormInput handles isDisabled', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: TextEditingController(), + label: 'Numeric Input', + isDisabled: true, + ), + ), + ), + ); + + // Verify that the widget is in disabled mode + expect((tester.widget(find.byType(DigitNumericFormInput)) as DigitNumericFormInput).isDisabled, true); + // You may want to add additional assertions specific to disabled mode. + }); + + testWidgets('DigitNumericFormInput updates value on direct input', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: TextEditingController(), + label: 'Numeric Input', + ), + ), + ), + ); + + // Find the widget in the tree + final numericInput = find.byType(DigitNumericFormInput); + + // Enter a numeric value directly into the input field + await tester.enterText(numericInput, '42'); + await tester.pump(); + + // Verify that the value has been updated + expect((tester.widget(numericInput) as DigitNumericFormInput).controller.text, '42'); + }); + + testWidgets('DigitNumericFormInput handles initial value', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: TextEditingController(), + initialValue: '42', + label: 'Numeric Input', + ), + ), + ), + ); + + // Find the widget in the tree + final numericInput = find.byType(DigitNumericFormInput); + + // Verify that the initial value is set correctly + expect((tester.widget(numericInput) as DigitNumericFormInput).controller.text, '42'); + }); + + testWidgets('DigitNumericFormInput handles charCount', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: TextEditingController(), + label: 'Numeric Input', + charCount: true, + ), + ), + ), + ); + + // Find the widget in the tree + final numericInput = find.byType(DigitNumericFormInput); + + // Enter a numeric value directly into the input field + await tester.enterText(numericInput, '42'); + await tester.pump(); + + // Verify that the charCount is displayed + expect(find.text('2/64'), findsOneWidget); + }); + + testWidgets('DigitNumericFormInput handles custom suffix and prefix icons', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: TextEditingController(), + label: 'Numeric Input', + suffixIcon: Icons.add_circle, + prefixIcon: Icons.remove_circle, + step: 1, + ), + ), + ), + ); + + // Find the widget in the tree + final numericInput = find.byType(DigitNumericFormInput); + + // Verify that custom suffix and prefix icons are displayed + expect(find.byIcon(Icons.add_circle), findsOneWidget); + expect(find.byIcon(Icons.remove_circle), findsOneWidget); + + // Tap on the suffix icon to increment value + await tester.tap(find.byIcon(Icons.add_circle)); + await tester.pump(); + + // Verify that the value has been incremented + expect((tester.widget(numericInput) as DigitNumericFormInput).controller.text, '1'); + + // Tap on the prefix icon to decrement value + await tester.tap(find.byIcon(Icons.remove_circle)); + await tester.pump(); + + // Verify that the value has been decremented + expect((tester.widget(numericInput) as DigitNumericFormInput).controller.text, '0'); + }); + + testWidgets('DigitNumericFormInput handles keyboard input', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: TextEditingController(), + label: 'Numeric Input', + ), + ), + ), + ); + + // Find the widget in the tree + final numericInput = find.byType(DigitNumericFormInput); + + // Enter a numeric value using the keyboard + await tester.enterText(numericInput, '123'); + await tester.pump(); + + // Verify that the value has been updated + expect((tester.widget(numericInput) as DigitNumericFormInput).controller.text, '123'); + }); + + testWidgets('DigitNumericFormInput handles onChanged callback', (WidgetTester tester) async { + String? changedValue; + + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: TextEditingController(), + label: 'Numeric Input', + onChange: (value) { + changedValue = value; + }, + ), + ), + ), + ); + + // Find the widget in the tree + final numericInput = find.byType(DigitNumericFormInput); + + // Enter a numeric value + await tester.enterText(numericInput, '42'); + await tester.pump(); + + // Verify that the onChanged callback has been called + expect(changedValue, '42'); + }); + + testWidgets('DigitNumericFormInput handles readOnly state', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitNumericFormInput( + controller: TextEditingController(), + label: 'Numeric Input', + readOnly: true, + initialValue: '0', + ), + ), + ), + ); + + // Find the widget in the tree + final numericInput = find.byType(DigitNumericFormInput); + + // Try to tap on the suffix icon to increment value (should not change in readOnly mode) + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that the value has not changed + expect((tester.widget(numericInput) as DigitNumericFormInput).controller.text, '0'); + }); + + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_password_form_input_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_password_form_input_test.dart new file mode 100644 index 00000000000..122c40aee13 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_password_form_input_test.dart @@ -0,0 +1,132 @@ +import 'package:digit_components/utils/validators/validator.dart'; +import 'package:digit_components/widgets/atoms/digit_password_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_text_form_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DigitTextFormInput Tests', () { + late TextEditingController controller; + + setUp(() { + controller = TextEditingController(); + }); + + testWidgets('Renders without error', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitPasswordFormInput( + controller: TextEditingController(), + ), + ), + ), + ); + + expect(find.byType(DigitPasswordFormInput), findsOneWidget); + }); + + testWidgets('Toggles visibility on suffix icon tap', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitPasswordFormInput( + controller: controller, + ), + ), + ), + ); + + expect(find.byIcon(Icons.visibility_off), findsOneWidget); + + await tester.tap(find.byIcon(Icons.visibility_off)); + await tester.pump(); + + expect(find.byIcon(Icons.visibility), findsOneWidget); + + // Perform other assertions based on your specific use case + }); + + testWidgets('Updates value on text input', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitPasswordFormInput( + controller: controller, + ), + ), + ), + ); + + await tester.enterText(find.byType(DigitPasswordFormInput), 'newPassword'); + expect(controller.text, 'newPassword'); + + // Additional value updating scenarios can be added here + }); + + testWidgets('Displays char count properly', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitPasswordFormInput( + controller: controller, + charCount: true, + ), + ), + ), + ); + + await tester.pump(); + + // Perform assertions based on char count display + expect(find.text('0/64'), findsOneWidget); + + + // Additional char count scenarios can be added here + }); + + testWidgets('Displays help text', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitPasswordFormInput( + controller: controller, + helpText: 'Enter a strong password', + ), + ), + ), + ); + + + await tester.pump(); + + // Perform assertions based on help text and error message + expect(find.text('Enter a strong password'), findsOneWidget); + + }); + + testWidgets('Handles readOnly state', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitPasswordFormInput( + controller: controller, + readOnly: true, + ), + ), + ), + ); + + await tester.tap(find.byIcon(Icons.visibility_off)); + expect(find.byType(DigitPasswordFormInput), findsOneWidget); + expect(find.byIcon(Icons.visibility), findsNothing); + expect(find.byIcon(Icons.visibility_off), findsOneWidget); + }); + + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_radio_list_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_radio_list_test.dart new file mode 100644 index 00000000000..683f8d901b9 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_radio_list_test.dart @@ -0,0 +1,107 @@ +import 'package:digit_components/models/RadioButtonModel.dart'; +import 'package:digit_components/widgets/atoms/digit_radio_list.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DigitRadioList Widget Tests', () { + testWidgets('Widget renders correctly', (WidgetTester tester) async { + // Build our widget and trigger a frame + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitRadioList( + radioButtons: [ + RadioButtonModel(code: 'A', name: 'Option A'), + RadioButtonModel(code: 'B', name: 'Option B'), + ], + onChanged: (selectedValue) {}, + ), + ), + ), + + ); + + // Verify that the widget renders correctly + expect(find.text('Option A'), findsOneWidget); + expect(find.text('Option B'), findsOneWidget); + }); + + testWidgets('Selecting a radio button triggers onChanged callback', (WidgetTester tester) async { + String selectedValue = ''; + + // Build our widget and trigger a frame + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitRadioList( + radioButtons: [ + RadioButtonModel(code: 'A', name: 'Option A'), + RadioButtonModel(code: 'B', name: 'Option B'), + ], + onChanged: (value) { + selectedValue = value; + }, + ), + ), + ), + + ); + + // Tap on the first radio button + await tester.tap(find.text('Option A')); + await tester.pump(); + + // Verify that the onChanged callback is called with the correct value + expect(selectedValue, 'A'); + }); + + testWidgets('Radio buttons are disabled when isDisabled is true', (WidgetTester tester) async { + // Build our widget with isDisabled set to true + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitRadioList( + radioButtons: [ + RadioButtonModel(code: 'A', name: 'Option A'), + RadioButtonModel(code: 'B', name: 'Option B'), + ], + onChanged: (selectedValue) {}, + isDisabled: true, + ), + ), + ), + ); + + // Verify that the radio buttons are disabled and selected value doesn't change + await tester.tap(find.text('Option A')); + await tester.pump(); + expect(tester.widget(find.byType(DigitRadioList)).groupValue, ''); // No change in selected value + }); + + testWidgets('Radio buttons are enabled when isDisabled is false', (WidgetTester tester) async { + // Build our widget with isDisabled set to false + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitRadioList( + radioButtons: [ + RadioButtonModel(code: 'A', name: 'Option A'), + RadioButtonModel(code: 'B', name: 'Option B'), + ], + onChanged: (selectedValue) {}, + isDisabled: false, + ), + ), + ), + ); + + // Verify that the radio buttons are enabled and selected value changes + await tester.tap(find.text('Option A')); + await tester.pump(); + expect(tester.widget(find.byType(DigitRadioList)).groupValue, 'A'); // Change in selected value + }); + + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_search_form_input_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_search_form_input_test.dart new file mode 100644 index 00000000000..0ff4e99b2e4 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_search_form_input_test.dart @@ -0,0 +1,149 @@ +import 'package:digit_components/utils/validators/validator.dart'; +import 'package:digit_components/widgets/atoms/digit_search_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_text_form_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DigitTextFormInput Tests', () { + late TextEditingController controller; + + setUp(() { + controller = TextEditingController(); + }); + + testWidgets('Renders without error', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitSearchFormInput( + controller: TextEditingController(), + ), + ), + ), + ); + + expect(find.byType(DigitSearchFormInput), findsOneWidget); + }); + + testWidgets('Displays search icon', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitSearchFormInput( + controller: TextEditingController(), + ), + ), + ), + ); + + expect(find.byIcon(Icons.search), findsOneWidget); + }); + + testWidgets('Calls onSuffixTap on suffix icon tap', (WidgetTester tester) async { + bool onTapCalled = false; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitSearchFormInput( + controller: TextEditingController(), + onSuffixTap: (value) { + onTapCalled = true; + }, + ), + ), + ), + ); + + await tester.tap(find.byIcon(Icons.search)); + await tester.pump(); + + expect(onTapCalled, isTrue); + }); + + testWidgets('Handles readOnly state', (WidgetTester tester) async { + bool onTapCalled = false; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitSearchFormInput( + controller: TextEditingController(), + readOnly: true, + onSuffixTap: (value) { + onTapCalled = true; + }, + ), + ), + ), + ); + + // Ensure the suffix icon is disabled when readOnly is true + expect(find.byIcon(Icons.search), findsOneWidget); + await tester.tap(find.byIcon(Icons.search)); + await tester.pump(); + + + expect(find.byIcon(Icons.search), findsOneWidget); + expect(onTapCalled, isFalse); + }); + + testWidgets('Displays help text', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitSearchFormInput( + controller: controller, + helpText: 'Enter search query', + ), + ), + ), + ); + + // Trigger validation by losing focus + await tester.tap(find.byType(TextFormField)); + await tester.pump(); + + // Perform assertions based on help text and error message + expect(find.text('Enter search query'), findsOneWidget); + expect(find.text('Search query cannot be empty'), findsNothing); + }); + + testWidgets('Updates value on text input', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitSearchFormInput( + controller: controller, + ), + ), + ), + ); + + await tester.enterText(find.byType(DigitSearchFormInput), 'newSearchText'); + expect(controller.text, 'newSearchText'); + }); + + testWidgets('Handles disabled state', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitSearchFormInput( + controller: controller, + isDisabled: true, + ), + ), + ), + ); + + await tester.enterText(find.byType(DigitSearchFormInput), 'newSearchText'); + await tester.pump(); + + // No change should occur in disabled state + expect(controller.text, ''); + }); + + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_textArea_form_input_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_textArea_form_input_test.dart new file mode 100644 index 00000000000..f95cd21e68f --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_textArea_form_input_test.dart @@ -0,0 +1,177 @@ +import 'package:digit_components/utils/validators/validator.dart'; +import 'package:digit_components/widgets/atoms/digit_text_area_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_text_form_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DigitTextFormInput Tests', () { + late TextEditingController controller; + + setUp(() { + controller = TextEditingController(); + }); + + testWidgets('Renders without error', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextAreaFormInput( + controller: TextEditingController(), + ), + ), + ), + ); + + expect(find.byType(DigitTextAreaFormInput), findsOneWidget); + }); + + testWidgets('Displays the specified number of lines', (WidgetTester tester) async { + const int minLines = 4; + const int maxLines = 8; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextAreaFormInput( + controller: TextEditingController(), + minLine: minLines, + maxLine: maxLines, + ), + ), + ), + ); + + final textField = tester.widget(find.byType(DigitTextAreaFormInput)); + expect(textField.minLine, minLines); + expect(textField.maxLine, maxLines); + }); + + testWidgets('Updates value on text input', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextAreaFormInput( + controller: controller, + ), + ), + ), + ); + + await tester.enterText(find.byType(DigitTextAreaFormInput), 'newTextAreaText'); + expect(controller.text, 'newTextAreaText'); + }); + + testWidgets('Handles readOnly state', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextAreaFormInput( + controller: TextEditingController(), + readOnly: true, + ), + ), + ), + ); + await tester.enterText(find.byType(DigitTextAreaFormInput), 'newTextAreaText'); + expect(controller.text, ''); + }); + + testWidgets('Handles disabled state', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextAreaFormInput( + controller: TextEditingController(), + isDisabled: true, + ), + ), + ), + ); + + await tester.enterText(find.byType(DigitTextAreaFormInput), 'newTextAreaText'); + expect(controller.text, ''); + }); + + testWidgets('Displays help text', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextAreaFormInput( + controller: controller, + helpText: 'Enter text', + ), + ), + ), + ); + + await tester.pump(); + + // Perform assertions based on help text + expect(find.text('Enter text'), findsOneWidget); + + }); + + testWidgets('Handles char count properly', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextAreaFormInput( + controller: controller, + charCount: true, + ), + ), + ), + ); + + // Trigger validation by losing focus + await tester.tap(find.byType(TextFormField)); + await tester.pump(); + + // Perform assertions based on char count display + expect(find.text('0/64'), findsOneWidget); + + }); + + testWidgets('Updates value on text input', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextAreaFormInput( + controller: controller, + ), + ), + ), + ); + + await tester.enterText(find.byType(DigitTextAreaFormInput), 'newTextAreaText'); + expect(controller.text, 'newTextAreaText'); + }); + + }); +} +// testWidgets('Location permission granted updates controller text', (WidgetTester tester) async { +// when(mockGeolocator.checkPermission()).thenAnswer((_) async => LocationPermission.whileInUse); +// when(mockGeolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high)) +// .thenAnswer((_) async => Position(latitude: 37.7749, longitude: -122.4194)); +// +// await tester.pumpWidget( +// MaterialApp( +// home: Scaffold( +// body: DigitLocationFormInput(controller: controller), +// ), +// ), +// ); +// +// // Tap the suffix icon to get location +// await tester.tap(find.byIcon(Icons.my_location)); +// +// // Allow time for the asynchronous operation to complete +// await tester.pumpAndSettle(); +// +// // Ensure the controller's text is updated with the expected location +// expect(controller.text, '37.7749, -122.4194'); +// }); \ No newline at end of file diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_text_form_input_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_text_form_input_test.dart new file mode 100644 index 00000000000..1964a02e843 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_text_form_input_test.dart @@ -0,0 +1,288 @@ +import 'package:digit_components/utils/validators/validator.dart'; +import 'package:digit_components/widgets/atoms/digit_text_form_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DigitTextFormInput Tests', () { + late TextEditingController controller; + + setUp(() { + controller = TextEditingController(); + }); + + late FocusNode myFocusNode; + + // testing rendering the widget + + testWidgets('Renders the widget', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextFormInput( + controller: controller, + label: 'Test Label', + ), + ), + ), + ); + + // Ensure the label is rendered + expect(find.text('Test Label'), findsOneWidget); + }); + + // testing validation and error message + + testWidgets('Validates input using provided validators on focus loss', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Column( + children: [ + DigitTextFormInput( + + controller: controller, + label: 'Test Label', + validations: [ + Validator(ValidatorType.pattern, r'^[a-zA-Z0-9]+$', errorMessage: 'Invalid format.'), + ], + ), + // Use a FlatButton to simulate taps + ElevatedButton( + onPressed: () { + // This is where you can put any logic you want + FocusScope.of(tester.element(find.byType(DigitTextFormInput))).unfocus(); + }, + child: Text('Tap me'), + ), + ], + ), + ), + ), + ); + + // Simulate typing + await tester.enterText(find.byType(DigitTextFormInput), '@@'); + await tester.pump(); + + // Tap on the FlatButton to simulate focus loss + await tester.tap(find.text('Tap me')); + + + // Ensure all asynchronous operations are completed + await tester.pumpAndSettle(); // Ensure all asynchronous operations are completed + + + // Ensure the validation error is shown after the focus loss + expect(find.text('Invalid format.'), findsOneWidget); + }); + + + // testing readonly and isdisabled states + + + testWidgets('Handles readOnly and isDisabled states', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Column( + children: [ + DigitTextFormInput( + key: Key('Editable Field'), + controller: controller, + label: 'Editable Field', + ), + DigitTextFormInput( + key: Key('ReadOnly Field'), + controller: controller, + label: 'ReadOnly Field', + readOnly: true, + ), + DigitTextFormInput( + key: Key('Disabled Field'), + controller: controller, + label: 'Disabled Field', + isDisabled: true, + ), + ], + ), + ), + ), + ); + + // Verify the editable field is enabled + expect( + tester + .widget(find.byKey(Key('Editable Field'))) + .readOnly, + isFalse, + ); + expect( + tester + .widget(find.byKey(Key('Editable Field'))) + .isDisabled, + isFalse, + ); + + // Verify the readOnly field is readOnly and not enabled + expect( + tester + .widget(find.byKey(Key('ReadOnly Field'))) + .readOnly, + isTrue, + ); + expect( + tester + .widget(find.byKey(Key('ReadOnly Field'))) + .isDisabled, + isFalse, + ); + + // Verify the disabled field is disabled and not enabled + expect( + tester + .widget(find.byKey(Key('Disabled Field'))) + .readOnly, + isFalse, + ); + expect( + tester + .widget(find.byKey(Key('Disabled Field'))) + .isDisabled, + isTrue, + ); + }); + + // testing tooltip behavior + + testWidgets('Handles tooltip behavior', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextFormInput( + controller: controller, + label: 'Test Label', + info: true, + infoText: 'Info Tooltip', + ), + ), + ), + ); + + // Verify the tooltip is not initially present + expect(find.text('Info Tooltip'), findsNothing); + + // Trigger the tooltip with a long press (simulate hover) + await tester.longPress(find.byIcon(Icons.info_outline)); + + // Pump the widget tree to allow time for the tooltip to appear + await tester.pump(); + + // Verify the tooltip is now present + expect(find.text('Info Tooltip'), findsOneWidget); + }); + + //Testing Char Count Display + + testWidgets('Displays character count if charCount is true', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextFormInput( + controller: controller, + label: 'Test Label', + charCount: true, + initialValue: 'helloworld', + validations: [ + Validator(ValidatorType.maxLength, 10, + errorMessage: 'Maximum length is 10.'), + ], + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + // Verify the updated character count is displayed (should be capped at 10) + expect(find.text('10/10'), findsOneWidget); + }); + + //Testing Prefix and Suffix Icons + + testWidgets('Displays prefix and suffix icons', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextFormInput( + controller: controller, + label: 'Test Label', + prefixIcon: Icons.person, + suffixIcon: Icons.visibility, + ), + ), + ), + ); + + // Verify the prefix and suffix icons are displayed + expect(find.byIcon(Icons.person), findsOneWidget); + expect(find.byIcon(Icons.visibility), findsOneWidget); + }); + + // Testing Prefix and Suffix Icon Click Handling + // + // testWidgets('Handles click events on prefix and suffix icons', (WidgetTester tester) async { + // bool prefixIconClicked = false; + // bool suffixIconClicked = false; + // + // await tester.pumpWidget( + // MaterialApp( + // home: Scaffold( + // body: DigitTextFormInput( + // controller: controller, + // label: 'Test Label', + // prefixIcon: Icons.person, + // suffixIcon: Icons.visibility, + // onPrefixIconClick: () { + // prefixIconClicked = true; + // }, + // onSuffixIconClick: () { + // suffixIconClicked = true; + // }, + // ), + // ), + // ), + // ); + // + // // Tap on prefix icon + // await tester.tap(find.byIcon(Icons.person)); + // expect(prefixIconClicked, isTrue); + // + // // Tap on suffix icon + // await tester.tap(find.byIcon(Icons.visibility)); + // expect(suffixIconClicked, isTrue); + // }); + + // Testing Initial Value + + testWidgets('Sets initial value correctly', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTextFormInput( + controller: controller, + label: 'Test Label', + initialValue: 'Initial', + ), + ), + ), + ); + + // Verify the initial value is set + expect(find.text('Initial'), findsOneWidget); + }); + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_time_form_input_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_time_form_input_test.dart new file mode 100644 index 00000000000..93970abf0b2 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_time_form_input_test.dart @@ -0,0 +1,144 @@ +import 'package:digit_components/utils/validators/validator.dart'; +import 'package:digit_components/widgets/atoms/digit_text_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_time_form_input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DigitTextFormInput Tests', () { + late TextEditingController controller; + + setUp(() { + controller = TextEditingController(); + }); + + testWidgets('Renders without error', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTimeFormInput( + controller: TextEditingController(), + ), + ), + ), + ); + + expect(find.byType(DigitTimeFormInput), findsOneWidget); + }); + + testWidgets('Displays time icon', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTimeFormInput( + controller: TextEditingController(), + ), + ), + ), + ); + + expect(find.byIcon(Icons.access_time), findsOneWidget); + }); + + testWidgets('Opens time picker on suffix icon tap', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTimeFormInput( + controller: controller, + ), + ), + ), + ); + + await tester.tap(find.byIcon(Icons.access_time)); + await tester.pumpAndSettle(); + + expect(find.byType(TimePickerDialog), findsOneWidget); + }); + + testWidgets('Updates controller value on time picker selection', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTimeFormInput( + controller: controller, + ), + ), + ), + ); + + await tester.tap(find.byIcon(Icons.access_time)); + await tester.pumpAndSettle(); + + // Select a time in the time picker + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Ensure that the controller's value is updated + expect(controller.text, isNotEmpty); + }); + + testWidgets('Formats time correctly', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTimeFormInput( + controller: controller, + ), + ), + ), + ); + + await tester.tap(find.byIcon(Icons.access_time)); + await tester.pumpAndSettle(); + + // Select a time in the time picker + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Ensure that the controller's value is formatted correctly + final formattedTimeRegex = RegExp(r'^\d{2}:\d{2} (AM|PM)$'); + expect(formattedTimeRegex.hasMatch(controller.text), isTrue); + }); + + testWidgets('Updates value on text input', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTimeFormInput( + controller: controller, + ), + ), + ), + ); + + await tester.enterText(find.byType(TextFormField), 'newTimeText'); + expect(controller.text, 'newTimeText'); + }); + + testWidgets('read only true', (WidgetTester tester) async { + final controller = TextEditingController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigitTimeFormInput( + controller: controller, + readOnly: true, + ), + ), + ), + ); + + await tester.tap(find.byIcon(Icons.access_time)); + await tester.pumpAndSettle(); + + expect(find.byType(TimePickerDialog), findsNothing); + }); + + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_tree_select_dropdown_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_tree_select_dropdown_test.dart new file mode 100644 index 00000000000..853f2924f5c --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/atoms/digit_tree_select_dropdown_test.dart @@ -0,0 +1,156 @@ +import 'package:digit_components/models/TreeModel.dart'; +import 'package:digit_components/widgets/atoms/digit_tree_select_dropdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('TreeSelectDropDown Tests', () { + + testWidgets('Renders TreeSelectDropDown correctly', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TreeSelectDropDown( + options: [], // Add some test options + onOptionSelected: (selectedOptions) {}, + ), + ), + ), + ); + + /// Verify if the widget renders correctly + expect(find.byType(TreeSelectDropDown), findsOneWidget); + }); + + testWidgets('Selecting an option updates the selected options', (WidgetTester tester) async { + List options = [ + TreeNode('1', 'Option 1', []), + TreeNode('2', 'Option 2', []), + TreeNode('3', 'Option 3', []), + ]; + + /// Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TreeSelectDropDown( + options: options, + onOptionSelected: (selectedOptions) {}, + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Get the offset of the center of the widget. + final Offset widgetCenter = tester.getCenter(find.text('Option 1')); + + /// Tap at the widget's center using tester.tapAt. + await tester.tapAt(widgetCenter); + await tester.pumpAndSettle(); + + /// Close the dropdown by tapping outside. + await tester.tap(find.byType(GestureDetector).first); + await tester.pumpAndSettle(); + /// Verify that the selected item is displayed. + + expect(find.text('Option 1'), findsWidgets); + }); + + testWidgets('Clearing selected options works as expected', (WidgetTester tester) async { + List options = [ + TreeNode('1', 'Option 1', []), + TreeNode('2', 'Option 2', []), + TreeNode('3', 'Option 3', []), + ]; + + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TreeSelectDropDown( + options: options, + onOptionSelected: (selectedOptions) {}, + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Get the offset of the center of the widget. + final Offset widgetCenter = tester.getCenter(find.text('Option 1')); + + /// Tap at the widget's center using tester.tapAt. + await tester.tapAt(widgetCenter); + await tester.pumpAndSettle(); + + /// Close the dropdown by tapping outside. + await tester.tap(find.byType(GestureDetector).first); + await tester.pumpAndSettle(); + + /// Verify that the selected item is displayed. + expect(find.text('1 Selected'), findsOneWidget); + expect(find.text('Option 1'), findsOneWidget); + + /// Clear the selection. + await tester.tap(find.text('Clear All')); + await tester.pump(); + + /// Verify that the selection is cleared. + expect(find.text('Option 1'), findsNothing); + }); + + testWidgets('Selecting multiple options works as expected', (WidgetTester tester) async { + List options = [ + TreeNode('1', 'Option 1', []), + TreeNode('2', 'Option 2', []), + TreeNode('3', 'Option 3', []), + ]; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TreeSelectDropDown( + options: options, + onOptionSelected: (selectedOptions) {}, + ), + ), + ), + ); + + /// Tap suffix icon to open the dropdown. + await tester.tap(find.byIcon(Icons.arrow_drop_down)); + await tester.pump(); + + /// Get the offset of the center of the widget. + final Offset widgetCenter = tester.getCenter(find.text('Option 1')); + + /// Tap at the widget's center using tester.tapAt. + await tester.tapAt(widgetCenter); + + + /// Get the offset of the center of the widget. + final Offset widgetCenter1 = tester.getCenter(find.text('Option 3')); + + /// Tap at the widget's center using tester.tapAt. + await tester.tapAt(widgetCenter1); + await tester.pumpAndSettle(); + + /// Close the dropdown by tapping outside. + await tester.tap(find.byType(GestureDetector).first); + await tester.pumpAndSettle(); + + /// Verify that the selected items are displayed. + expect(find.text('2 Selected'), findsOneWidget); + }); + + + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/powered_by_digit_test.dart b/flutter/digit-ui-components/digit_components/test/widgets/powered_by_digit_test.dart new file mode 100644 index 00000000000..5223fe06c80 --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/powered_by_digit_test.dart @@ -0,0 +1,27 @@ +import 'package:digit_components/digit_components.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'widget_app.dart'; + +void main() { + group('Powered by Digit widget', () { + testWidgets('Widget is created without errors', (widgetTester) async { + await widgetTester.pumpWidget(const WidgetApp( + child: PoweredByDigit( + version: '12.3', + ))); + + expect( + find.widgetWithImage( + PoweredByDigit, + const AssetImage( + 'assets/images/powered_by_digit.png', + package: 'digit_components', + ), + ), + findsOneWidget, + ); + }); + }); +} diff --git a/flutter/digit-ui-components/digit_components/test/widgets/widget_app.dart b/flutter/digit-ui-components/digit_components/test/widgets/widget_app.dart new file mode 100644 index 00000000000..58d6107a65a --- /dev/null +++ b/flutter/digit-ui-components/digit_components/test/widgets/widget_app.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class WidgetApp extends StatelessWidget { + final Widget child; + final NavigatorObserver? navigatorObserver; + + const WidgetApp({ + super.key, + required this.child, + this.navigatorObserver, + }); + + @override + Widget build(BuildContext context) => MaterialApp( + navigatorObservers: [if (navigatorObserver != null) navigatorObserver!], + home: Scaffold(body: child), + ); +} diff --git a/flutter/digit-ui-components/example/.gitignore b/flutter/digit-ui-components/example/.gitignore new file mode 100644 index 00000000000..24476c5d1eb --- /dev/null +++ b/flutter/digit-ui-components/example/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/flutter/digit-ui-components/example/.metadata b/flutter/digit-ui-components/example/.metadata new file mode 100644 index 00000000000..e94891d5fbf --- /dev/null +++ b/flutter/digit-ui-components/example/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: android + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: ios + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: linux + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: macos + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: web + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: windows + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/flutter/digit-ui-components/example/README.md b/flutter/digit-ui-components/example/README.md new file mode 100644 index 00000000000..2b3fce4c86a --- /dev/null +++ b/flutter/digit-ui-components/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/flutter/digit-ui-components/example/analysis_options.yaml b/flutter/digit-ui-components/example/analysis_options.yaml new file mode 100644 index 00000000000..61b6c4de17c --- /dev/null +++ b/flutter/digit-ui-components/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/flutter/digit-ui-components/example/android/.gitignore b/flutter/digit-ui-components/example/android/.gitignore new file mode 100644 index 00000000000..6f568019d3c --- /dev/null +++ b/flutter/digit-ui-components/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/flutter/digit-ui-components/example/android/app/build.gradle b/flutter/digit-ui-components/example/android/app/build.gradle new file mode 100644 index 00000000000..4c908ebc119 --- /dev/null +++ b/flutter/digit-ui-components/example/android/app/build.gradle @@ -0,0 +1,72 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + namespace "com.example.example" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/flutter/digit-ui-components/example/android/app/src/debug/AndroidManifest.xml b/flutter/digit-ui-components/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000000..399f6981d5d --- /dev/null +++ b/flutter/digit-ui-components/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/flutter/digit-ui-components/example/android/app/src/main/AndroidManifest.xml b/flutter/digit-ui-components/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..19b862ec8a7 --- /dev/null +++ b/flutter/digit-ui-components/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/flutter/digit-ui-components/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/flutter/digit-ui-components/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 00000000000..e793a000d6a --- /dev/null +++ b/flutter/digit-ui-components/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/flutter/digit-ui-components/example/android/app/src/main/res/drawable-v21/launch_background.xml b/flutter/digit-ui-components/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000000..f74085f3f6a --- /dev/null +++ b/flutter/digit-ui-components/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/flutter/digit-ui-components/example/android/app/src/main/res/drawable/launch_background.xml b/flutter/digit-ui-components/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000000..304732f8842 --- /dev/null +++ b/flutter/digit-ui-components/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..db77bb4b7b0 Binary files /dev/null and b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..17987b79bb8 Binary files /dev/null and b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..09d4391482b Binary files /dev/null and b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..d5f1c8d34e7 Binary files /dev/null and b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..4d6372eebdb Binary files /dev/null and b/flutter/digit-ui-components/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/flutter/digit-ui-components/example/android/app/src/main/res/values-night/styles.xml b/flutter/digit-ui-components/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000000..06952be745f --- /dev/null +++ b/flutter/digit-ui-components/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/flutter/digit-ui-components/example/android/app/src/main/res/values/styles.xml b/flutter/digit-ui-components/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000000..cb1ef88056e --- /dev/null +++ b/flutter/digit-ui-components/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/flutter/digit-ui-components/example/android/app/src/profile/AndroidManifest.xml b/flutter/digit-ui-components/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000000..399f6981d5d --- /dev/null +++ b/flutter/digit-ui-components/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/flutter/digit-ui-components/example/android/build.gradle b/flutter/digit-ui-components/example/android/build.gradle new file mode 100644 index 00000000000..f7eb7f63ce1 --- /dev/null +++ b/flutter/digit-ui-components/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/flutter/digit-ui-components/example/android/gradle.properties b/flutter/digit-ui-components/example/android/gradle.properties new file mode 100644 index 00000000000..94adc3a3f97 --- /dev/null +++ b/flutter/digit-ui-components/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/flutter/digit-ui-components/example/android/gradle/wrapper/gradle-wrapper.properties b/flutter/digit-ui-components/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..3c472b99c6f --- /dev/null +++ b/flutter/digit-ui-components/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/flutter/digit-ui-components/example/android/settings.gradle b/flutter/digit-ui-components/example/android/settings.gradle new file mode 100644 index 00000000000..44e62bcf06a --- /dev/null +++ b/flutter/digit-ui-components/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/flutter/digit-ui-components/example/assets/fonts/Roboto-Regular.ttf b/flutter/digit-ui-components/example/assets/fonts/Roboto-Regular.ttf new file mode 100644 index 00000000000..ddf4bfacb39 Binary files /dev/null and b/flutter/digit-ui-components/example/assets/fonts/Roboto-Regular.ttf differ diff --git a/flutter/digit-ui-components/example/ios/.gitignore b/flutter/digit-ui-components/example/ios/.gitignore new file mode 100644 index 00000000000..7a7f9873ad7 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/flutter/digit-ui-components/example/ios/Flutter/AppFrameworkInfo.plist b/flutter/digit-ui-components/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000000..9625e105df3 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/flutter/digit-ui-components/example/ios/Flutter/Debug.xcconfig b/flutter/digit-ui-components/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000000..592ceee85b8 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/flutter/digit-ui-components/example/ios/Flutter/Release.xcconfig b/flutter/digit-ui-components/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000000..592ceee85b8 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.pbxproj b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..1495d2b5451 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,613 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..919434a6254 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..f9b0d7c5ea1 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/flutter/digit-ui-components/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000000..e42adcb34c2 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/digit-ui-components/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/flutter/digit-ui-components/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..1d526a16ed0 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/flutter/digit-ui-components/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter/digit-ui-components/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/flutter/digit-ui-components/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter/digit-ui-components/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..f9b0d7c5ea1 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/flutter/digit-ui-components/example/ios/Runner/AppDelegate.swift b/flutter/digit-ui-components/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000000..70693e4a8c1 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..d36b1fab2d9 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000000..dc9ada4725e Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000000..7353c41ecf9 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000000..797d452e458 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000000..6ed2d933e11 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000000..4cd7b0099ca Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000000..fe730945a01 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000000..321773cd857 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000000..797d452e458 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000000..502f463a9bc Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000000..0ec30343922 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000000..0ec30343922 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000000..e9f5fea27c7 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000000..84ac32ae7d9 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000000..8953cba0906 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000000..0467bf12aa4 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000000..0bedcf2fd46 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000000..9da19eacad3 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000000..9da19eacad3 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000000..9da19eacad3 Binary files /dev/null and b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000000..89c2725b70f --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/flutter/digit-ui-components/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/flutter/digit-ui-components/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000000..f2e259c7c93 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/digit-ui-components/example/ios/Runner/Base.lproj/Main.storyboard b/flutter/digit-ui-components/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000000..f3c28516fb3 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/digit-ui-components/example/ios/Runner/Info.plist b/flutter/digit-ui-components/example/ios/Runner/Info.plist new file mode 100644 index 00000000000..7f553465b77 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/flutter/digit-ui-components/example/ios/Runner/Runner-Bridging-Header.h b/flutter/digit-ui-components/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000000..308a2a560b4 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/flutter/digit-ui-components/example/ios/RunnerTests/RunnerTests.swift b/flutter/digit-ui-components/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000000..86a7c3b1b61 --- /dev/null +++ b/flutter/digit-ui-components/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/flutter/digit-ui-components/example/lib/main.dart b/flutter/digit-ui-components/example/lib/main.dart new file mode 100644 index 00000000000..cae0638a4fa --- /dev/null +++ b/flutter/digit-ui-components/example/lib/main.dart @@ -0,0 +1,600 @@ +import 'package:digit_components/enum/app_enums.dart'; +import 'package:digit_components/models/DropdownModels.dart'; +import 'package:digit_components/models/RadioButtonModel.dart'; +import 'package:digit_components/models/TreeModel.dart'; +import 'package:digit_components/models/toggleButtonModel.dart'; +import 'package:digit_components/theme/digit_theme.dart'; +import 'package:digit_components/utils/validators/validator.dart'; +import 'package:digit_components/widgets/atoms/digit_button.dart'; +import 'package:digit_components/widgets/atoms/digit_checkbox.dart'; +import 'package:digit_components/widgets/atoms/digit_date_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_dropdown_input.dart'; +import 'package:digit_components/widgets/atoms/digit_location_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_multiselect_dropdown.dart'; +import 'package:digit_components/widgets/atoms/digit_numeric_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_password_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_radio_list.dart'; +import 'package:digit_components/widgets/atoms/digit_search_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_text_area_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_text_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_time_form_input.dart'; +import 'package:digit_components/widgets/atoms/digit_toggle_list.dart'; +import 'package:digit_components/widgets/atoms/digit_tree_select_dropdown.dart'; +import 'package:digit_components/widgets/scrollable_content.dart'; +import 'package:flutter/material.dart'; + + +final List Nodes = [ + TreeNode('A', 'A', [ + TreeNode('A.A1', 'A1', [ + TreeNode('A.A1.A3', 'A3', [ + TreeNode('A.A1.A3.A5', 'A5', []), + TreeNode('A.A1.A3.A6', 'A6', []), + ]), + TreeNode('A.A1.A4', 'A4', []), + ]), + TreeNode('A.A2', 'A2', []), + ]), + TreeNode('B', 'B', [ + TreeNode('B.B1', 'B1', []), + TreeNode('B.B2', 'B2', []), + ]), + TreeNode('C', 'C', [ + TreeNode('C.C1', 'C1', []), + TreeNode('C.C2', 'C2', []), + ]), + TreeNode('D', 'D', [ + TreeNode('D.D1', 'D1', []), + TreeNode('D.D2', 'D2', []), + ]), +]; + +final TreeSelectController _controller = TreeSelectController(); +final controller = MultiSelectController(); + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + /// This widget is the root of the application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Custom Text Field Example', + theme: DigitTheme.instance.mobileTheme, + home: const MyHomePage(title: 'Digit Components Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + final String title; + const MyHomePage({required this.title, super.key}); + + @override + MyHomePageState createState() => MyHomePageState(); +} + +class MyHomePageState extends State { + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title, textAlign: TextAlign.center,), + ), + body:ScrollableContent( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + DigitTextFormInput( + label: "input", + initialValue: 'value', + controller: TextEditingController(), + innerLabel: 'label', + helpText: 'help text', + charCount: true, + validations: [ + Validator(ValidatorType.maxLength, 10, + errorMessage: 'Maximum length is 10.'), + Validator(ValidatorType.pattern, r'^[a-zA-Z0-9]+$', + errorMessage: 'Invalid format.'), + ], + ), + const SizedBox( + height: 8, + ), + DigitTextAreaFormInput( + label: "input", + controller: TextEditingController(), + // state: 'Disabled', + // info: 'this is info', + innerLabel: 'label', + helpText: 'help text', + ), + const SizedBox( + height: 8, + ), + DigitDateFormInput( + label: "input", + controller: TextEditingController(), + innerLabel: 'innerlabel', + helpText: 'help text', + ), + const SizedBox( + height: 8, + ), + DigitTimeFormInput( + label: "input", + controller: TextEditingController(), + // state: 'Disabled', + // info: 'this is info', + innerLabel: 'innerlabel', + helpText: 'help text', + ), + const SizedBox( + height: 8, + ), + DigitSearchFormInput( + label: "input", + controller: TextEditingController(), + innerLabel: 'innerlabel', + helpText: 'help text', + ), + const SizedBox( + height: 8, + ), + DigitPasswordFormInput( + label: "input", + controller: TextEditingController(), + innerLabel: 'innerlabel', + helpText: 'help text', + validations: [ + Validator(ValidatorType.minLength, 6, + errorMessage: + 'Password must be at least 6 characters.'), + ], + ), + DigitTextFormInput( + label: "input", + controller: TextEditingController(), + // state: 'Disabled', + // info: 'this is info', + innerLabel: 'innerlabel', + helpText: 'help text', + // isDisabled: true, + // onSuffixTap: (){print('tapppppppppp');}, + suffixIcon: Icons.currency_rupee_sharp, + prefixIcon: Icons.currency_rupee, + ), + const SizedBox( + height: 8, + ), + DigitNumericFormInput( + label: "input", + controller: TextEditingController(), + // state: 'Disabled', + innerLabel: 'innerlabel', + helpText: 'help text', + initialValue: '0', + // readOnly: true, + ), + const SizedBox( + height: 8, + ), + DigitLocationFormInput( + label: "input", + controller: TextEditingController(), + // state: 'Disabled', + // info: 'this is info', + innerLabel: 'innerlabel', + helpText: 'help text', + ), + const SizedBox( + height: 16, + ), + const SizedBox( + height: 8, + ), + DigitDropdown( + onChange: (String value, String index) => { + print(value), + print(index), + }, + textEditingController: TextEditingController(), + textIcon: Icons.article, + dropdownType: DropdownType.profileSelect, + items: const [ + DropdownItem( + name: 'first', + code: '1', + description: 'description for first one', + profileImage: NetworkImage( + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSzBXNuO6PezhC18aYH_2cYtS0I7KbxoKYdwA&usqp=CAU', + ), + ), + DropdownItem( + name: 'second', + code: '2', + description: 'description for second one', + profileImage: NetworkImage( + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSzBXNuO6PezhC18aYH_2cYtS0I7KbxoKYdwA&usqp=CAU', + ), + ), + ], + ), + const SizedBox( + height: 16, + ), + DigitDropdown( + onChange: (String value, String index) => { + print(value), + print(index), + }, + textEditingController: TextEditingController(), + textIcon: Icons.article, + items: [ + 'apple', + 'banana', + 'orange', + 'grapes', + ] + .asMap() + .entries + .map( + (item) => DropdownItem( + name: item.value, + code: item.key.toString(), + description: 'description for ${item.value} one', + ), + ) + .toList(), + ), + DigitDropdown( + onChange: (String value, String index) => { + print(value), + print(index), + }, + textEditingController: TextEditingController(), + items: [ + 'apple', + 'banana', + 'orange', + 'grapes', + ] + .asMap() + .entries + .map( + (item) => DropdownItem( + name: item.value, + code: item.key.toString(), + ), + ) + .toList(), + ), + const SizedBox( + height: 16, + ), + DigitDropdown( + onChange: (String value, String type) => { + print(value), + print(type), + }, + textEditingController: TextEditingController(), + dropdownType: DropdownType.nestedSelect, + items: const [ + DropdownItem( + name: 'apple', + code: '1', + type: 'group B', + ), + DropdownItem( + name: 'grapes', + code: '2', + type: 'group A', + ), + DropdownItem( + name: 'banana', + code: '3', + type: 'group B', + ), + DropdownItem( + name: 'papaya', + code:'4', + type: 'group A', + ), + DropdownItem( + name: 'pine apple', + code: '4', + type: 'group B', + ), + DropdownItem( + name: 'watermelon', + code: '5', + type: 'group A', + ), + ], + ), + const SizedBox( + height: 16, + ), + TreeSelectDropDown( + onOptionSelected: (List selectedOptions) { + // print(selectedOptions); + for (TreeNode node in selectedOptions) { + // print("Node: ${node.value}"); + } + }, + options: Nodes, + treeSelectionType: TreeSelectionType.MultiSelect, + // chipConfig: const ChipConfig(wrapType: WrapType.wrap), + controller: _controller, + ), + const SizedBox( + height: 16, + ), + TreeSelectDropDown( + onOptionSelected: (List selectedOptions) { + // print(selectedOptions); + for (TreeNode node in selectedOptions) { + // print("Node: ${node.value}"); + } + }, + options: Nodes, + treeSelectionType: TreeSelectionType.singleSelect, + ), + const SizedBox( + height: 16, + ), + MultiSelectDropDown( + onOptionSelected: + (List selectedOptions) {}, + options: const [ + DropdownItem(code: '1', name: 'firstddddddddddddd'), + DropdownItem( + code: '2', name: 'seconddddddddddddddddd'), + DropdownItem(code: '3', name: 'thiraaaaaaaaaaaad'), + DropdownItem(code: '4', name: 'foussssssssr'), + DropdownItem(code: '5', name: 'fivssssssssssse'), + ], + selectionType: SelectionType.multiSelect, + ), + MultiSelectDropDown( + onOptionSelected: + (List selectedOptions) {}, + options: const [ + DropdownItem(code: '1', name: 'firstddddddddddddd'), + DropdownItem( + code: '2', name: 'seconddddddddddddddddd'), + DropdownItem(code: '3', name: 'thiraaaaaaaaaaaad'), + DropdownItem(code: '4', name: 'foussssssssr'), + DropdownItem(code: '5', name: 'fivssssssssssse'), + ], + selectionType: SelectionType.multiSelect, + ), + MultiSelectDropDown( + onOptionSelected: + (List selectedOptions) {}, + options: const [ + DropdownItem( + code: '1', + name: 'firstddddddddddddd', + description: 'ddddddddddddddddddddd', + ), + DropdownItem( + code: '2', + name: 'seconddddddddddddddddd', + description: 'ddddddddddddddddddddd', + ), + DropdownItem( + code: '3', + name: 'thiraaaaaaaaaaaad', + description: 'ddddddddddddddddddddd', + ), + DropdownItem( + code: '4', + name: 'foussssssssr', + description: 'ddddddddddddddddddddd', + ), + DropdownItem( + code: '5', + name: 'fivssssssssssse', + description: 'ddddddddddddddddddddd', + ), + ], + selectionType: SelectionType.multiSelect, + ), + const SizedBox( + height: 16, + ), + MultiSelectDropDown( + onOptionSelected: + (List selectedOptions) {}, + options: const [ + DropdownItem( + code: '1', + name: 'firstddddddddddddd', + type: 'aaaaa', + ), + DropdownItem( + code: '2', + name: 'seconddddddddddddddddd', + type: 'aaaaa', + ), + DropdownItem( + code: '3', + name: 'thiraaaaaaaaaaaad', + type: 'bbbbb', + ), + DropdownItem( + code: '4', + name: 'foussssssssr', + type: 'aaaaa', + ), + DropdownItem( + code: '5', + name: 'fivssssssssssse', + type: 'bbbbb', + ), + ], + selectionType: SelectionType.nestedMultiSelect, + ), + const SizedBox( + height: 8, + ), + DigitRadioList( + onChanged: (value) { + // print(value); + }, + radioButtons: [ + RadioButtonModel( + code: '1', + name: 'One', + ), + RadioButtonModel(code: '2', name: 'Two'), + RadioButtonModel(code: '3', name: 'Three'), + // Add more radio buttons as needed + ], + ), + const SizedBox( + height: 16, + ), + DigitToggleList( + toggleButtons: [ + ToggleButtonModel( + name: 'Toggle 1', + key: 'key1', + onSelected: () { + print('Toggle 1 selected!'); + }), + ToggleButtonModel( + name: 'Toggle 2', + key: 'key2', + onSelected: () { + print('Toggle 2 selected!'); + }), + ToggleButtonModel( + name: 'Toggle 3', + key: 'key3', + onSelected: () { + print('Toggle 3 selected!'); + }), + ], + onChanged: (selectedValues) { + print('Selected values: $selectedValues'); + }, + ), + const SizedBox( + height: 16, + ), + DigitButton( + prefixIcon: Icons.add, + label: 'Primary Button', + onPressed: () { + // Add your primary button logic here + print('Primary button pressed'); + }, + type: ButtonType.primary, + ), + const SizedBox(height: 16), + DigitButton( + label: 'Secondary Button', + onPressed: () { + // Add your secondary button logic here + print('Secondary button pressed'); + }, + type: ButtonType.secondary, + ), + const SizedBox(height: 16), + DigitButton( + label: 'Link', + onPressed: () { + // Add your secondary button logic here + print('link pressed'); + }, + type: ButtonType.link, + ), + const SizedBox(height: 16), + DigitButton( + label: 'tertiary Button', + onPressed: () { + // Add your secondary button logic here + print('tertiary button pressed'); + }, + type: ButtonType.tertiary, + ), + DigitButton( + prefixIcon: Icons.add, + label: 'Primary Button', + onPressed: () { + print('Primary button pressed'); + }, + type: ButtonType.primary, + isDisabled: true, + ), + const SizedBox(height: 16), + DigitButton( + label: 'Secondary Button', + onPressed: () { + print('Secondary button pressed'); + }, + isDisabled: true, + type: ButtonType.secondary, + ), + const SizedBox(height: 16), + DigitButton( + label: 'Link', + onPressed: () { + print('link pressed'); + }, + isDisabled: true, + type: ButtonType.link, + ), + const SizedBox(height: 16), + DigitButton( + label: 'tertiary Button', + onPressed: () { + print('tertiary button pressed'); + }, + isDisabled: true, + type: ButtonType.tertiary, + ), + const SizedBox(height: 16), + DigitCheckbox( + label: 'checkbox', + value: false, + onChanged: (value) { + print(value); + }, + ), + ], + ), + ), + ], + ), + ); + + } + +} + +// void main() { +// runApp( +// MaterialApp( +// home: Scaffold( +// body: +// ), +// ), +// ); +// } diff --git a/flutter/digit-ui-components/example/linux/.gitignore b/flutter/digit-ui-components/example/linux/.gitignore new file mode 100644 index 00000000000..d3896c98444 --- /dev/null +++ b/flutter/digit-ui-components/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/flutter/digit-ui-components/example/linux/CMakeLists.txt b/flutter/digit-ui-components/example/linux/CMakeLists.txt new file mode 100644 index 00000000000..d67bd4e03e2 --- /dev/null +++ b/flutter/digit-ui-components/example/linux/CMakeLists.txt @@ -0,0 +1,139 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/flutter/digit-ui-components/example/linux/flutter/CMakeLists.txt b/flutter/digit-ui-components/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000000..d5bd01648a9 --- /dev/null +++ b/flutter/digit-ui-components/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/flutter/digit-ui-components/example/linux/flutter/generated_plugin_registrant.cc b/flutter/digit-ui-components/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000000..e71a16d23d0 --- /dev/null +++ b/flutter/digit-ui-components/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/flutter/digit-ui-components/example/linux/flutter/generated_plugin_registrant.h b/flutter/digit-ui-components/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000000..e0f0a47bc08 --- /dev/null +++ b/flutter/digit-ui-components/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter/digit-ui-components/example/linux/flutter/generated_plugins.cmake b/flutter/digit-ui-components/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000000..2e1de87a7eb --- /dev/null +++ b/flutter/digit-ui-components/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/flutter/digit-ui-components/example/linux/main.cc b/flutter/digit-ui-components/example/linux/main.cc new file mode 100644 index 00000000000..e7c5c543703 --- /dev/null +++ b/flutter/digit-ui-components/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/flutter/digit-ui-components/example/linux/my_application.cc b/flutter/digit-ui-components/example/linux/my_application.cc new file mode 100644 index 00000000000..0ba8f43096d --- /dev/null +++ b/flutter/digit-ui-components/example/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/flutter/digit-ui-components/example/linux/my_application.h b/flutter/digit-ui-components/example/linux/my_application.h new file mode 100644 index 00000000000..72271d5e417 --- /dev/null +++ b/flutter/digit-ui-components/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/flutter/digit-ui-components/example/macos/.gitignore b/flutter/digit-ui-components/example/macos/.gitignore new file mode 100644 index 00000000000..746adbb6b9e --- /dev/null +++ b/flutter/digit-ui-components/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/flutter/digit-ui-components/example/macos/Flutter/Flutter-Debug.xcconfig b/flutter/digit-ui-components/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000000..c2efd0b608b --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/flutter/digit-ui-components/example/macos/Flutter/Flutter-Release.xcconfig b/flutter/digit-ui-components/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000000..c2efd0b608b --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/flutter/digit-ui-components/example/macos/Flutter/GeneratedPluginRegistrant.swift b/flutter/digit-ui-components/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000000..4074530e2eb --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import geolocator_apple +import location +import package_info_plus +import path_provider_foundation +import speech_to_text_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) + LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SpeechToTextMacosPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextMacosPlugin")) +} diff --git a/flutter/digit-ui-components/example/macos/Runner.xcodeproj/project.pbxproj b/flutter/digit-ui-components/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..79158a24796 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/flutter/digit-ui-components/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter/digit-ui-components/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/flutter/digit-ui-components/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter/digit-ui-components/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000000..8fedab682d2 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/digit-ui-components/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/flutter/digit-ui-components/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..1d526a16ed0 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/flutter/digit-ui-components/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter/digit-ui-components/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/flutter/digit-ui-components/example/macos/Runner/AppDelegate.swift b/flutter/digit-ui-components/example/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000000..d53ef643772 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..a2ec33f19f1 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000000..82b6f9d9a33 Binary files /dev/null and b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000000..13b35eba55c Binary files /dev/null and b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000000..0a3f5fa40fb Binary files /dev/null and b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000000..bdb57226d5f Binary files /dev/null and b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000000..f083318e09c Binary files /dev/null and b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000000..326c0e72c9d Binary files /dev/null and b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000000..2f1632cfddf Binary files /dev/null and b/flutter/digit-ui-components/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/flutter/digit-ui-components/example/macos/Runner/Base.lproj/MainMenu.xib b/flutter/digit-ui-components/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000000..80e867a4e06 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/digit-ui-components/example/macos/Runner/Configs/AppInfo.xcconfig b/flutter/digit-ui-components/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000000..92fb3cd54e8 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/flutter/digit-ui-components/example/macos/Runner/Configs/Debug.xcconfig b/flutter/digit-ui-components/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000000..36b0fd9464f --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/flutter/digit-ui-components/example/macos/Runner/Configs/Release.xcconfig b/flutter/digit-ui-components/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000000..dff4f49561c --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/flutter/digit-ui-components/example/macos/Runner/Configs/Warnings.xcconfig b/flutter/digit-ui-components/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000000..42bcbf4780b --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/flutter/digit-ui-components/example/macos/Runner/DebugProfile.entitlements b/flutter/digit-ui-components/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000000..dddb8a30c85 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/flutter/digit-ui-components/example/macos/Runner/Info.plist b/flutter/digit-ui-components/example/macos/Runner/Info.plist new file mode 100644 index 00000000000..4789daa6a44 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/flutter/digit-ui-components/example/macos/Runner/MainFlutterWindow.swift b/flutter/digit-ui-components/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000000..3cc05eb2349 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/flutter/digit-ui-components/example/macos/Runner/Release.entitlements b/flutter/digit-ui-components/example/macos/Runner/Release.entitlements new file mode 100644 index 00000000000..852fa1a4728 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/flutter/digit-ui-components/example/macos/RunnerTests/RunnerTests.swift b/flutter/digit-ui-components/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000000..5418c9f5395 --- /dev/null +++ b/flutter/digit-ui-components/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/flutter/digit-ui-components/example/pubspec.lock b/flutter/digit-ui-components/example/pubspec.lock new file mode 100644 index 00000000000..48081afee0c --- /dev/null +++ b/flutter/digit-ui-components/example/pubspec.lock @@ -0,0 +1,1366 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" + url: "https://pub.dev" + source: hosted + version: "47.0.0" + accessibility_tools: + dependency: transitive + description: + name: accessibility_tools + sha256: "0a16adc8dfa3a7ebd38775135d86443011a65d4ecbb438913e4992b5d29135fe" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "0c284758213de797488b570a8e0feaa6546b0e8dd189be6128d49bb0413d021a" + url: "https://pub.dev" + source: hosted + version: "0.10.0" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + archive: + dependency: transitive + description: + name: archive + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + url: "https://pub.dev" + source: hosted + version: "3.4.10" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + auto_route: + dependency: "direct main" + description: + name: auto_route + sha256: "12047baeca0e01df93165ef33275b32119d72699ab9a49dc64c20e78f586f96d" + url: "https://pub.dev" + source: hosted + version: "5.0.4" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + sha256: c66eaa20dbba3211cac656037f88ba836a633dda953d9f4f9f9f5809b57e4278 + url: "https://pub.dev" + source: hosted + version: "5.0.2" + bloc: + dependency: transitive + description: + name: bloc + sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" + url: "https://pub.dev" + source: hosted + version: "8.1.2" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + sha256: "02f04270be5abae8df171143e61a0058a7acbce5dcac887612e89bb40cca4c33" + url: "https://pub.dev" + source: hosted + version: "9.1.5" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + url: "https://pub.dev" + source: hosted + version: "2.0.10" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + url: "https://pub.dev" + source: hosted + version: "2.4.8" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "30859c90e9ddaccc484f56303931f477b1f1ba2bab74aa32ed5d6ce15870f8cf" + url: "https://pub.dev" + source: hosted + version: "7.2.8" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309 + url: "https://pub.dev" + source: hosted + version: "8.8.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" + source: hosted + version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c" + url: "https://pub.dev" + source: hosted + version: "0.3.5" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" + collection: + dependency: "direct main" + description: + name: collection + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + url: "https://pub.dev" + source: hosted + version: "1.17.1" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb" + url: "https://pub.dev" + source: hosted + version: "1.6.4" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + dart_code_metrics: + dependency: "direct dev" + description: + name: dart_code_metrics + sha256: "3a01766c3925b884a171a396f115f48977e367fe656de065ed7012f3d9540273" + url: "https://pub.dev" + source: hosted + version: "4.19.2" + dart_mappable: + dependency: "direct main" + description: + name: dart_mappable + sha256: "606ffca123ef2051759fe3501fd5a868bbbb10b15e7b49663f5a982fdcd2943f" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + dart_mappable_builder: + dependency: "direct dev" + description: + name: dart_mappable_builder + sha256: "09073d7e9f84a5c42241cd0fc8388309cfb46ca73e8402e3ed3f9a8c4dbf9472" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + device_frame: + dependency: transitive + description: + name: device_frame + sha256: afe76182aec178d171953d9b4a50a43c57c7cf3c77d8b09a48bf30c8fa04dd9d + url: "https://pub.dev" + source: hosted + version: "1.1.0" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" + digit_components: + dependency: "direct main" + description: + path: "../digit_components" + relative: true + source: path + version: "0.0.1+8" + drift: + dependency: transitive + description: + name: drift + sha256: "43ae515270f38ffe47702dc920c04d415894bcc6ebdf3e2b6a292cb207ed8cb7" + url: "https://pub.dev" + source: hosted + version: "1.7.1" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: "7b8945ba99a1ff56b606123d5ae21d2ef912e8d1486ce571ca6e3ed2ec1a8482" + url: "https://pub.dev" + source: hosted + version: "1.7.0" + easy_stepper: + dependency: transitive + description: + name: easy_stepper + sha256: "77f3ab4ee3c867b5a2236bf712abb08fed2b1c533cf24cf3fcd46c2821072ffd" + url: "https://pub.dev" + source: hosted + version: "0.5.2+1" + expandable: + dependency: "direct main" + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: transitive + description: + name: flutter_bloc + sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae + url: "https://pub.dev" + source: hosted + version: "8.1.3" + flutter_keyboard_visibility: + dependency: "direct main" + description: + name: flutter_keyboard_visibility + sha256: "4983655c26ab5b959252ee204c2fffa4afeb4413cd030455194ec0caa3b8e7cb" + url: "https://pub.dev" + source: hosted + version: "5.4.1" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: ce0e501cfc258907842238e4ca605e74b7fd1cdf04b3b43e86c43f3e40a1592c + url: "https://pub.dev" + source: hosted + version: "0.11.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_spinkit: + dependency: transitive + description: + name: flutter_spinkit + sha256: b39c753e909d4796906c5696a14daf33639a76e017136c8d82bf3e620ce5bb8e + url: "https://pub.dev" + source: hosted + version: "5.2.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_typeahead: + dependency: transitive + description: + name: flutter_typeahead + sha256: b9942bd5b7611a6ec3f0730c477146cffa4cd4b051077983ba67ddfc9e7ee818 + url: "https://pub.dev" + source: hosted + version: "4.8.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: transitive + description: + name: fluttertoast + sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1 + url: "https://pub.dev" + source: hosted + version: "8.2.4" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "4179d41127bc7a67dc3f58ceec1d22f1cdf10470653cb86eda2a63f81b4920c7" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + url: "https://pub.dev" + source: hosted + version: "2.4.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + gap: + dependency: transitive + description: + name: gap + sha256: db02ec4ac4511ea8d324d7f671d526959a8e7857468b4ea64113fe8a82f16a2c + url: "https://pub.dev" + source: hosted + version: "2.0.2" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: e946395fc608842bb2f6c914807e9183f86f3cb787f6b8f832753e5251036f02 + url: "https://pub.dev" + source: hosted + version: "10.1.0" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: "741579fa6c9e412984d2bdb2fbaa54e3c3f7587c60aeacfe6e058358a11f40f8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: "16cdb6b8d3685d3e07a7e54e88e97106f4fae9e496191f33e8bb389cce14f198" + url: "https://pub.dev" + source: hosted + version: "2.3.4" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "6c8d494d6948757c56720b778af742f6973f31fca1f702a7539b8917e4a2468a" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: "59083f7e0871b78299918d92bf930a14377f711d2d1156c558cd5ebae6c20d58" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: a92fae29779d5c6dc60e8411302f5221ade464968fe80a36d330e80a71f087af + url: "https://pub.dev" + source: hosted + version: "0.2.2" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + google_fonts: + dependency: transitive + description: + name: google_fonts + sha256: "2776c66b3e97c6cdd58d1bd3281548b074b64f1fd5c8f82391f7456e38849567" + url: "https://pub.dev" + source: hosted + version: "4.0.5" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + horizontal_data_table: + dependency: transitive + description: + name: horizontal_data_table + sha256: c8ab5256bbced698a729f3e0ff2cb0e8e97416cdbb082860370eaf883badf722 + url: "https://pub.dev" + source: hosted + version: "4.3.1" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" + url: "https://pub.dev" + source: hosted + version: "3.3.0" + inspector: + dependency: transitive + description: + name: inspector + sha256: "40ba0ac1c819c85139bfec9d1e283804581a8985c91f19d00e93212cf29226b1" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + intl: + dependency: transitive + description: + name: intl + sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 + url: "https://pub.dev" + source: hosted + version: "0.18.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + isar: + dependency: transitive + description: + name: isar + sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + isar_generator: + dependency: "direct dev" + description: + name: isar_generator + sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "3520fa844009431b5d4491a5a778603520cdc399ab3406332dcc50f93547258c" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: f3c2c18a7889580f71926f30c1937727c8c7d4f3a435f8f5e8b0ddd25253ef5d + url: "https://pub.dev" + source: hosted + version: "6.5.4" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + location: + dependency: transitive + description: + name: location + sha256: "06be54f682c9073cbfec3899eb9bc8ed90faa0e17735c9d9fa7fe426f5be1dd1" + url: "https://pub.dev" + source: hosted + version: "5.0.3" + location_platform_interface: + dependency: transitive + description: + name: location_platform_interface + sha256: "8aa1d34eeecc979d7c9fe372931d84f6d2ebbd52226a54fe1620de6fdc0753b1" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + location_web: + dependency: transitive + description: + name: location_web + sha256: ec484c66e8a4ff1ee5d044c203f4b6b71e3a0556a97b739a5bc9616de672412b + url: "https://pub.dev" + source: hosted + version: "4.2.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + lottie: + dependency: transitive + description: + name: lottie + sha256: b8bdd54b488c54068c57d41ae85d02808da09e2bee8b8dd1f59f441e7efa60cd + url: "https://pub.dev" + source: hosted + version: "2.6.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + url: "https://pub.dev" + source: hosted + version: "0.12.15" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + material_design_icons_flutter: + dependency: "direct main" + description: + name: material_design_icons_flutter + sha256: "4c81ebf55b9661d1eb94652884a7b098af63153ea75b9070caedaddfae308e02" + url: "https://pub.dev" + source: hosted + version: "6.0.7296" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" + url: "https://pub.dev" + source: hosted + version: "0.3.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + overlay_builder: + dependency: transitive + description: + name: overlay_builder + sha256: "58b97bc5f67a2e2bb7006dd88e697ac757dfffc9dbd1e7dfc1917fb510a4b5c8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + pedantic: + dependency: transitive + description: + name: pedantic + sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointer_interceptor: + dependency: transitive + description: + name: pointer_interceptor + sha256: acfcd63c00ec3d5a7894b0e2a875893716d31958fe03f064734dba7dfd9113d9 + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + url: "https://pub.dev" + source: hosted + version: "6.1.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + qr_code_scanner: + dependency: "direct main" + description: + name: qr_code_scanner + sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd + url: "https://pub.dev" + source: hosted + version: "1.0.1" + reactive_flutter_typeahead: + dependency: transitive + description: + name: reactive_flutter_typeahead + sha256: ef91627df8cef70e603e8a6458749d8a99a385b78854332602fd08ad905cdab8 + url: "https://pub.dev" + source: hosted + version: "0.8.1" + reactive_forms: + dependency: "direct main" + description: + name: reactive_forms + sha256: "5aa9c48a0626c20d00a005e597cb10efbdebbfeecb9c4227b03a5945fbb91ec4" + url: "https://pub.dev" + source: hosted + version: "14.3.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + resizable_widget: + dependency: transitive + description: + name: resizable_widget + sha256: db2919754b93f386b9b3fb15e9f48f6c9d6d41f00a24397629133c99df86606a + url: "https://pub.dev" + source: hosted + version: "1.0.5" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + url: "https://pub.dev" + source: hosted + version: "1.2.6" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + speech_to_text: + dependency: "direct main" + description: + name: speech_to_text + sha256: e2c2667088a9800ffb504dbb34997314e3cae2f92ad5ed76c49124fb1bc6edac + url: "https://pub.dev" + source: hosted + version: "6.5.1" + speech_to_text_macos: + dependency: transitive + description: + name: speech_to_text_macos + sha256: "6b5575e5a8346be1779838b0a482c259474965b5943668830b479147a75b5bfc" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + speech_to_text_platform_interface: + dependency: transitive + description: + name: speech_to_text_platform_interface + sha256: "2ef9c0abf3b4340998fcb489afc4fc8cd7574eff21d912673be59b60ff16850c" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "281b672749af2edf259fc801f0fcba092257425bcd32a0ce1c8237130bc934c7" + url: "https://pub.dev" + source: hosted + version: "1.11.2" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: "9ed8f4a24a2a243e23ad267bb50378cb75c7de0b200b5336229b2d6096e6a5df" + url: "https://pub.dev" + source: hosted + version: "0.22.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + url: "https://pub.dev" + source: hosted + version: "1.24.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + url: "https://pub.dev" + source: hosted + version: "0.5.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + time: + dependency: transitive + description: + name: time + sha256: "83427e11d9072e038364a5e4da559e85869b227cf699a541be0da74f14140124" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + type_plus: + dependency: transitive + description: + name: type_plus + sha256: d8e0d6b20fac9afcfd0848064b2523b162c0d53e4c0b890ef51323b2ed50b290 + url: "https://pub.dev" + source: hosted + version: "0.6.3" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" + url: "https://pub.dev" + source: hosted + version: "4.2.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + url: "https://pub.dev" + source: hosted + version: "11.10.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + widgetbook: + dependency: "direct dev" + description: + name: widgetbook + sha256: be0588cd864a70bd2817ca2fab0dbd147c460b712cfe53fd13e6fd46b5c3f368 + url: "https://pub.dev" + source: hosted + version: "3.7.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + url: "https://pub.dev" + source: hosted + version: "5.0.9" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/flutter/digit-ui-components/example/pubspec.yaml b/flutter/digit-ui-components/example/pubspec.yaml new file mode 100644 index 00000000000..1f3e062c625 --- /dev/null +++ b/flutter/digit-ui-components/example/pubspec.yaml @@ -0,0 +1,55 @@ +name: example +description: A new Flutter project. +publish_to: 'none' +version: 1.0.0 + +environment: + sdk: '>=3.0.0 <=4.0.0' + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.2 + digit_components: ^0.0.1 + auto_route: ^5.0.2 + collection: ^1.16.0 + reactive_forms: ^14.1.0 + dart_mappable: ^1.2.0 + flutter_keyboard_visibility: ^5.4.0 + material_design_icons_flutter: ^6.0.7096 + speech_to_text: ^6.1.1 + expandable: ^5.0.1 + qr_code_scanner: ^1.0.1 + geolocator: ^10.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + dart_code_metrics: ^4.19.1 + freezed: ^2.1.0+1 + build_runner: ^2.2.1 + json_serializable: ^6.4.0 + drift_dev: ^1.7.0 + auto_route_generator: ^5.0.2 + bloc_test: ^9.1.0 + mocktail: ^0.3.0 + dart_mappable_builder: ^1.2.1 + isar_generator: ^3.0.5 + flutter_launcher_icons: ^0.11.0 + widgetbook: ^3.4.1 + +dependency_overrides: + intl: ^0.18.0 + + +flutter: + assets: + - ./ + + fonts: + - family: Roboto + fonts: + - asset: assets/fonts/Roboto-Regular.ttf + + uses-material-design: true diff --git a/flutter/digit-ui-components/example/pubspec_overrides.yaml b/flutter/digit-ui-components/example/pubspec_overrides.yaml new file mode 100644 index 00000000000..308f685ccec --- /dev/null +++ b/flutter/digit-ui-components/example/pubspec_overrides.yaml @@ -0,0 +1,3 @@ +dependency_overrides: + digit_components: + path: ../digit_components \ No newline at end of file diff --git a/flutter/digit-ui-components/example/test/widget_test.dart b/flutter/digit-ui-components/example/test/widget_test.dart new file mode 100644 index 00000000000..47fa4b831e2 --- /dev/null +++ b/flutter/digit-ui-components/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// // This is a basic Flutter widget test. +// // +// // To perform an interaction with a widget in your test, use the WidgetTester +// // utility in the flutter_test package. For example, you can send tap and scroll +// // gestures. You can also use WidgetTester to find child widgets in the widget +// // tree, read text, and verify that the values of widget properties are correct. +// +// import 'package:flutter/material.dart'; +// import 'package:flutter_test/flutter_test.dart'; +// +// import 'package:example/main.dart'; +// +// void main() { +// testWidgets('Counter increments smoke test', (WidgetTester tester) async { +// // Build our app and trigger a frame. +// await tester.pumpWidget(const MyApp()); +// +// // Verify that our counter starts at 0. +// expect(find.text('0'), findsOneWidget); +// expect(find.text('1'), findsNothing); +// +// // Tap the '+' icon and trigger a frame. +// await tester.tap(find.byIcon(Icons.add)); +// await tester.pump(); +// +// // Verify that our counter has incremented. +// expect(find.text('0'), findsNothing); +// expect(find.text('1'), findsOneWidget); +// }); +// } diff --git a/flutter/digit-ui-components/example/web/favicon.png b/flutter/digit-ui-components/example/web/favicon.png new file mode 100644 index 00000000000..8aaa46ac1ae Binary files /dev/null and b/flutter/digit-ui-components/example/web/favicon.png differ diff --git a/flutter/digit-ui-components/example/web/icons/Icon-192.png b/flutter/digit-ui-components/example/web/icons/Icon-192.png new file mode 100644 index 00000000000..b749bfef074 Binary files /dev/null and b/flutter/digit-ui-components/example/web/icons/Icon-192.png differ diff --git a/flutter/digit-ui-components/example/web/icons/Icon-512.png b/flutter/digit-ui-components/example/web/icons/Icon-512.png new file mode 100644 index 00000000000..88cfd48dff1 Binary files /dev/null and b/flutter/digit-ui-components/example/web/icons/Icon-512.png differ diff --git a/flutter/digit-ui-components/example/web/icons/Icon-maskable-192.png b/flutter/digit-ui-components/example/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000000..eb9b4d76e52 Binary files /dev/null and b/flutter/digit-ui-components/example/web/icons/Icon-maskable-192.png differ diff --git a/flutter/digit-ui-components/example/web/icons/Icon-maskable-512.png b/flutter/digit-ui-components/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000000..d69c56691fb Binary files /dev/null and b/flutter/digit-ui-components/example/web/icons/Icon-maskable-512.png differ diff --git a/flutter/digit-ui-components/example/web/index.html b/flutter/digit-ui-components/example/web/index.html new file mode 100644 index 00000000000..be820e83eba --- /dev/null +++ b/flutter/digit-ui-components/example/web/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + + + + diff --git a/flutter/digit-ui-components/example/web/manifest.json b/flutter/digit-ui-components/example/web/manifest.json new file mode 100644 index 00000000000..096edf8fe4c --- /dev/null +++ b/flutter/digit-ui-components/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/flutter/digit-ui-components/example/windows/.gitignore b/flutter/digit-ui-components/example/windows/.gitignore new file mode 100644 index 00000000000..d492d0d98c8 --- /dev/null +++ b/flutter/digit-ui-components/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/flutter/digit-ui-components/example/windows/CMakeLists.txt b/flutter/digit-ui-components/example/windows/CMakeLists.txt new file mode 100644 index 00000000000..137867272ca --- /dev/null +++ b/flutter/digit-ui-components/example/windows/CMakeLists.txt @@ -0,0 +1,102 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/flutter/digit-ui-components/example/windows/flutter/CMakeLists.txt b/flutter/digit-ui-components/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000000..930d2071a32 --- /dev/null +++ b/flutter/digit-ui-components/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/flutter/digit-ui-components/example/windows/flutter/generated_plugin_registrant.cc b/flutter/digit-ui-components/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000000..1ece8f252d3 --- /dev/null +++ b/flutter/digit-ui-components/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + GeolocatorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("GeolocatorWindows")); +} diff --git a/flutter/digit-ui-components/example/windows/flutter/generated_plugin_registrant.h b/flutter/digit-ui-components/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000000..dc139d85a93 --- /dev/null +++ b/flutter/digit-ui-components/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/flutter/digit-ui-components/example/windows/flutter/generated_plugins.cmake b/flutter/digit-ui-components/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000000..7f101a778fa --- /dev/null +++ b/flutter/digit-ui-components/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + geolocator_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/flutter/digit-ui-components/example/windows/runner/CMakeLists.txt b/flutter/digit-ui-components/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000000..394917c053a --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/flutter/digit-ui-components/example/windows/runner/Runner.rc b/flutter/digit-ui-components/example/windows/runner/Runner.rc new file mode 100644 index 00000000000..687e6bd2109 --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/flutter/digit-ui-components/example/windows/runner/flutter_window.cpp b/flutter/digit-ui-components/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000000..b25e363efa4 --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/flutter_window.cpp @@ -0,0 +1,66 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/flutter/digit-ui-components/example/windows/runner/flutter_window.h b/flutter/digit-ui-components/example/windows/runner/flutter_window.h new file mode 100644 index 00000000000..6da0652f05f --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/flutter/digit-ui-components/example/windows/runner/main.cpp b/flutter/digit-ui-components/example/windows/runner/main.cpp new file mode 100644 index 00000000000..a61bf80d31f --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/flutter/digit-ui-components/example/windows/runner/resource.h b/flutter/digit-ui-components/example/windows/runner/resource.h new file mode 100644 index 00000000000..66a65d1e4a7 --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/flutter/digit-ui-components/example/windows/runner/resources/app_icon.ico b/flutter/digit-ui-components/example/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000000..c04e20caf63 Binary files /dev/null and b/flutter/digit-ui-components/example/windows/runner/resources/app_icon.ico differ diff --git a/flutter/digit-ui-components/example/windows/runner/runner.exe.manifest b/flutter/digit-ui-components/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000000..a42ea7687cb --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/flutter/digit-ui-components/example/windows/runner/utils.cpp b/flutter/digit-ui-components/example/windows/runner/utils.cpp new file mode 100644 index 00000000000..b2b08734db2 --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/flutter/digit-ui-components/example/windows/runner/utils.h b/flutter/digit-ui-components/example/windows/runner/utils.h new file mode 100644 index 00000000000..3879d547557 --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/flutter/digit-ui-components/example/windows/runner/win32_window.cpp b/flutter/digit-ui-components/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000000..60608d0fe5b --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/flutter/digit-ui-components/example/windows/runner/win32_window.h b/flutter/digit-ui-components/example/windows/runner/win32_window.h new file mode 100644 index 00000000000..e901dde684e --- /dev/null +++ b/flutter/digit-ui-components/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_