From ceb17555e1f8ca549c12f3eaeaac9977a1961fee Mon Sep 17 00:00:00 2001 From: Yelaman Yelmuratov Date: Sun, 11 Feb 2024 06:41:25 +0600 Subject: [PATCH] first commit --- .fvm/flutter_sdk | 1 + .fvm/fvm_config.json | 4 + .gitignore | 35 + .metadata | 10 + .vscode/settings.json | 7 + CHANGELOG.md | 10 + LICENSE | 8 + README.md | 121 + analysis_options.yaml | 4 + assets/images/rich_text_field.png | Bin 0 -> 8034 bytes assets/images/screenshot1.png | Bin 0 -> 116199 bytes assets/images/screenshot2.png | Bin 0 -> 118966 bytes assets/images/screenshot3.png | Bin 0 -> 120549 bytes example/.gitignore | 43 + example/.metadata | 33 + example/README.md | 16 + example/analysis_options.yaml | 28 + example/android/.gitignore | 13 + example/android/app/build.gradle | 67 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 33 + .../example/example/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + example/android/build.gradle | 30 + example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + example/android/settings.gradle | 29 + example/ios/.gitignore | 34 + example/ios/Flutter/AppFrameworkInfo.plist | 26 + example/ios/Flutter/Debug.xcconfig | 1 + example/ios/Flutter/Release.xcconfig | 1 + example/ios/Runner.xcodeproj/project.pbxproj | 617 ++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 + .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + example/ios/Runner/Base.lproj/Main.storyboard | 26 + example/ios/Runner/Info.plist | 49 + example/ios/Runner/Runner-Bridging-Header.h | 1 + example/ios/RunnerTests/RunnerTests.swift | 12 + example/lib/main.dart | 91 + example/pubspec.lock | 188 + example/pubspec.yaml | 25 + example/test/widget_test.dart | 30 + lib/rtf_textfield.dart | 8 + lib/src/core/models/text_delta.dart | 59 + lib/src/core/models/text_metadata.dart | 245 + lib/src/core/utils/converter.dart | 58 + lib/src/core/utils/enums/text_decoration.dart | 19 + lib/src/core/utils/enums/text_metadata.dart | 12 + lib/src/core/utils/extensions/color.dart | 7 + lib/src/core/utils/extensions/iterable.dart | 13 + lib/src/core/utils/extensions/list.dart | 47 + lib/src/core/utils/extensions/string.dart | 16 + lib/src/core/utils/extensions/text_align.dart | 14 + .../core/utils/extensions/text_deltas.dart | 28 + lib/src/core/utils/text_deltas.dart | 30 + lib/src/view/base/base_text_form_field.dart | 403 ++ lib/src/view/base/base_textfield.dart | 1707 ++++++ .../view/controller/base_rtf_controller.dart | 446 ++ .../controller/rtf_text_field_controller.dart | 94 + .../input_decoration/rtf_input_decorator.dart | 5041 +++++++++++++++++ lib/src/view/textfield/rtf_text_field.dart | 155 + .../view/textfield/rtf_text_form_field.dart | 199 + pubspec.yaml | 19 + test/rich_textfield_test.dart | 1 + 101 files changed, 10646 insertions(+) create mode 120000 .fvm/flutter_sdk create mode 100644 .fvm/fvm_config.json create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 .vscode/settings.json create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 assets/images/rich_text_field.png create mode 100644 assets/images/screenshot1.png create mode 100644 assets/images/screenshot2.png create mode 100644 assets/images/screenshot3.png create mode 100644 example/.gitignore create mode 100644 example/.metadata create mode 100644 example/README.md create mode 100644 example/analysis_options.yaml create mode 100644 example/android/.gitignore create mode 100644 example/android/app/build.gradle create mode 100644 example/android/app/src/debug/AndroidManifest.xml create mode 100644 example/android/app/src/main/AndroidManifest.xml create mode 100644 example/android/app/src/main/kotlin/com/richtextfield/example/example/MainActivity.kt create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/values-night/styles.xml create mode 100644 example/android/app/src/main/res/values/styles.xml create mode 100644 example/android/app/src/profile/AndroidManifest.xml create mode 100644 example/android/build.gradle create mode 100644 example/android/gradle.properties create mode 100644 example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 example/android/settings.gradle create mode 100644 example/ios/.gitignore create mode 100644 example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 example/ios/Flutter/Debug.xcconfig create mode 100644 example/ios/Flutter/Release.xcconfig create mode 100644 example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 example/ios/Runner/AppDelegate.swift create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 example/ios/Runner/Info.plist create mode 100644 example/ios/Runner/Runner-Bridging-Header.h create mode 100644 example/ios/RunnerTests/RunnerTests.swift create mode 100644 example/lib/main.dart create mode 100644 example/pubspec.lock create mode 100644 example/pubspec.yaml create mode 100644 example/test/widget_test.dart create mode 100644 lib/rtf_textfield.dart create mode 100644 lib/src/core/models/text_delta.dart create mode 100644 lib/src/core/models/text_metadata.dart create mode 100644 lib/src/core/utils/converter.dart create mode 100644 lib/src/core/utils/enums/text_decoration.dart create mode 100644 lib/src/core/utils/enums/text_metadata.dart create mode 100644 lib/src/core/utils/extensions/color.dart create mode 100644 lib/src/core/utils/extensions/iterable.dart create mode 100644 lib/src/core/utils/extensions/list.dart create mode 100644 lib/src/core/utils/extensions/string.dart create mode 100644 lib/src/core/utils/extensions/text_align.dart create mode 100644 lib/src/core/utils/extensions/text_deltas.dart create mode 100644 lib/src/core/utils/text_deltas.dart create mode 100644 lib/src/view/base/base_text_form_field.dart create mode 100644 lib/src/view/base/base_textfield.dart create mode 100644 lib/src/view/controller/base_rtf_controller.dart create mode 100644 lib/src/view/controller/rtf_text_field_controller.dart create mode 100644 lib/src/view/input_decoration/rtf_input_decorator.dart create mode 100644 lib/src/view/textfield/rtf_text_field.dart create mode 100644 lib/src/view/textfield/rtf_text_form_field.dart create mode 100644 pubspec.yaml create mode 100644 test/rich_textfield_test.dart diff --git a/.fvm/flutter_sdk b/.fvm/flutter_sdk new file mode 120000 index 0000000..ad8a2ac --- /dev/null +++ b/.fvm/flutter_sdk @@ -0,0 +1 @@ +/Users/yelamanyelmuratov/fvm/versions/3.16.9 \ No newline at end of file diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json new file mode 100644 index 0000000..1ba52d2 --- /dev/null +++ b/.fvm/fvm_config.json @@ -0,0 +1,4 @@ +{ + "flutterSdkVersion": "3.16.9", + "flavors": {} + } \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57bbdac --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# 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/ +build/ + +# Explicitly track these files, even if others are ignored +!lib/rich_textfield.dart +!lib/src/decoration/rich_input_decorator.dart +!lib/src/textfield/rich_text_form_field.dart +!lib/src/textfield/rich_textfield.dart diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..b2c661a --- /dev/null +++ b/.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: "67457e669f79e9f8d13d7a68fe09775fefbb79f4" + channel: "stable" + +project_type: package diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..12899a5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "dart.additionalAnalyzerFileExtensions": [ + "drift" + ], + "dart.lineLength": 80, + "dart.flutterSdkPath": ".fvm/flutter_sdk" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a7aecca --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +## 1.0.1 +Features: + - Customizable hint + - Customizable label + - Data serialization + - Customizable text features with RTFTextFieldController +Changes: + - Updated README file + - Refactoring + - Bug fix \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7d6ae9d --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright © 2023, shodev.live + +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/README.md b/README.md new file mode 100644 index 0000000..9ff6785 --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +
+

+ + + +

+
+ +

Flutter custom widget to create Rich TextField 🚀

+ +

+Included RTFTextFieldController for customize text, hint and label TextSpan 😊 +
+ Show some ❤️ and star the repo to support the project! +

+ +

+ Pub + License: MIT + Repository views + Pub +

+

+ Pub likes + Pub popularity + Pub points +

+ +
+ +## 📌 Features + +- ✅ Customizable hint +- ✅ Customizable label +- ✅ Data serialization *(Store and fetch styled text in JSON format)* +- ✅ Customizable text features with `RTFTextFieldController` *(change color, style, size, wight, etc.)* + +## 📌 Getting Started +Follow these steps to use this package + +### Add dependency + +```yaml +dependencies: + rtf_textfield: ^1.0.1 +``` + +### Add import package + +```dart +import 'package:rtf_textfield/rtf_textfield.dart'; +``` + +### Easy to use +Simple example of use `RTFTextField`
+Put this code in your project at an screen and learn how it works 😊 + +
+ Screenshot + Screenshot + Screenshot +
+ +  + +Widget part: +```dart +RTFTextField( + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + decoration: const RichInputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + ), + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + labelTextSpan: TextSpan( + text: 'Enter your name', + children: [ + TextSpan( + text: ' *', + style: TextStyle( + color: Colors.red, + ), + ), + ], + ), + hintTextSpan: TextSpan( + text: 'Yelaman', + ), + ), + controller: controller, +), +``` + +Change text weight using `RTFTextFieldController`: + +```dart +controller.toggleBold(); +``` + + +### 📌 Examples +You can check more examples of using this package [here](example/lib) + +
+
+

Thanks to all contributors of this package

+ + + +
+
\ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/assets/images/rich_text_field.png b/assets/images/rich_text_field.png new file mode 100644 index 0000000000000000000000000000000000000000..c82b8263820f5d235239d0f34ba72f405b20ca9c GIT binary patch literal 8034 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 literal 0 HcmV?d00001 diff --git a/assets/images/screenshot1.png b/assets/images/screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..001c746fc9e591bf425d9147065755bd3d7e54a9 GIT binary patch literal 116199 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 NQ`_K1!OwQV{|iuYKVbj> literal 0 HcmV?d00001 diff --git a/assets/images/screenshot3.png b/assets/images/screenshot3.png new file mode 100644 index 0000000000000000000000000000000000000000..a0c240de4eafbf84a1c3d9fd6418d98e867e1d6d GIT binary patch literal 120549 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 literal 0 HcmV?d00001 diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,43 @@ +# 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 +.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/example/.metadata b/example/.metadata new file mode 100644 index 0000000..5eba523 --- /dev/null +++ b/example/.metadata @@ -0,0 +1,33 @@ +# 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: "67457e669f79e9f8d13d7a68fe09775fefbb79f4" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 + base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 + - platform: android + create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 + base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 + - platform: ios + create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 + base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4 + + # 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/example/README.md b/example/README.md new file mode 100644 index 0000000..2b3fce4 --- /dev/null +++ b/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/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# 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.dev/lints. + # + # 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/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/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/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..be52613 --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.richtextfield.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.richtextfield.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 {} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..19b862e --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/richtextfield/example/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/richtextfield/example/example/MainActivity.kt new file mode 100644 index 0000000..46ce145 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/richtextfield/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.richtextfield.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + 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/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/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/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/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/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/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 + 12.0 + + diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6ed2618 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,617 @@ +// !$*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 = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + 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 = 12.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)"; + DEVELOPMENT_TEAM = BX9A4BFW2T; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.richtextfield.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.richtextfield.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.richtextfield.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.richtextfield.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 = 12.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 = 12.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)"; + DEVELOPMENT_TEAM = BX9A4BFW2T; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.richtextfield.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)"; + DEVELOPMENT_TEAM = BX9A4BFW2T; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.richtextfield.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/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/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/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/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/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/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/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/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/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist new file mode 100644 index 0000000..5458fc4 --- /dev/null +++ b/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + 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 + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/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/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..0b246c7 --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:rtf_textfield/rtf_textfield.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + final RTFTextFieldController controller = RTFTextFieldController(); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Image.asset( + '../assets/images/rich_text_field.png', + height: 50, + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + RTFTextField( + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + decoration: const RichInputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + ), + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + labelTextSpan: TextSpan( + text: 'Enter your name', + children: [ + TextSpan( + text: ' *', + style: TextStyle( + color: Colors.red, + ), + ), + ], + ), + hintTextSpan: TextSpan( + text: 'Yelaman', + ), + ), + controller: controller, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + controller.toggleBold(); + }, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..b165cbd --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,188 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" + source: hosted + version: "1.10.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + rtf_textfield: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" +sdks: + dart: ">=3.2.5 <4.0.0" + flutter: ">=1.17.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..e31f7cc --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,25 @@ +name: example +description: "A new Flutter project." +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 + +environment: + sdk: '>=3.2.5 <4.0.0' + +dependencies: + flutter: + sdk: flutter + rtf_textfield: + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true + + assets: + - ../assets/images/ \ No newline at end of file diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..092d222 --- /dev/null +++ b/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/lib/rtf_textfield.dart b/lib/rtf_textfield.dart new file mode 100644 index 0000000..ad04848 --- /dev/null +++ b/lib/rtf_textfield.dart @@ -0,0 +1,8 @@ +library rtf_textfield; + +export 'src/view/base/base_text_form_field.dart'; +export 'src/view/base/base_textfield.dart'; +export 'src/view/controller/rtf_text_field_controller.dart'; +export 'src/view/input_decoration/rtf_input_decorator.dart'; +export 'src/view/textfield/rtf_text_field.dart'; +export 'src/view/textfield/rtf_text_form_field.dart'; diff --git a/lib/src/core/models/text_delta.dart b/lib/src/core/models/text_delta.dart new file mode 100644 index 0000000..173ef94 --- /dev/null +++ b/lib/src/core/models/text_delta.dart @@ -0,0 +1,59 @@ +import 'package:rtf_textfield/src/core/models/text_metadata.dart'; + +/// `RTFTextDelta` is a class that holds the character and its metadata. +class RTFTextDelta { + final String char; + final RTFTextMetadata? metadata; + + const RTFTextDelta({ + required this.char, + this.metadata, + }); + + RTFTextDelta copyWith({ + String? char, + RTFTextMetadata? metadata, + }) { + return RTFTextDelta( + char: char ?? this.char, + metadata: metadata ?? this.metadata, + ); + } + + @override + String toString() { + return ''' +RTFTextDelta( + char: $char +)'''; + } + + Map toMap() { + return { + 'char': char, + 'metadata': metadata?.toMap(), + }; + } + + factory RTFTextDelta.fromMap(Map map) { + return RTFTextDelta( + char: map['char'] as String, + metadata: map['metadata'] == null + ? null + : RTFTextMetadata.fromMap( + (map['metadata'] as Map).cast(), + ), + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RTFTextDelta && + runtimeType == other.runtimeType && + char == other.char && + metadata == other.metadata; + + @override + int get hashCode => char.hashCode ^ metadata.hashCode; +} diff --git a/lib/src/core/models/text_metadata.dart b/lib/src/core/models/text_metadata.dart new file mode 100644 index 0000000..70fd61a --- /dev/null +++ b/lib/src/core/models/text_metadata.dart @@ -0,0 +1,245 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:rtf_textfield/src/core/utils/converter.dart'; +import 'package:rtf_textfield/src/core/utils/enums/text_decoration.dart'; +import 'package:rtf_textfield/src/core/utils/enums/text_metadata.dart'; +import 'package:rtf_textfield/src/core/utils/extensions/color.dart'; + +/// `RTFTextMetadata` is a class that holds the style change for each character. +class RTFTextMetadata { + final Color color; + final FontWeight fontWeight; + final FontStyle fontStyle; + final double fontSize; + final RTFTextDecorationEnum decoration; + final List? fontFeatures; + final TextAlign alignment; + + const RTFTextMetadata({ + this.color = Colors.black, + this.fontWeight = FontWeight.w400, + this.fontStyle = FontStyle.normal, + this.fontSize = 14, + this.alignment = TextAlign.start, + this.decoration = RTFTextDecorationEnum.none, + this.fontFeatures, + }); + + RTFTextMetadata.fromTextStyle( + TextStyle style, { + this.alignment = TextAlign.start, + }) : color = style.color ?? Colors.black, + fontWeight = style.fontWeight ?? FontWeight.w400, + fontStyle = style.fontStyle ?? FontStyle.normal, + fontSize = style.fontSize ?? 14, + decoration = style.decoration == null + ? RTFTextDecorationEnum.none + : RTFTextDecorationEnum.fromDecoration(style.decoration!), + fontFeatures = style.fontFeatures; + + RTFTextMetadata copyWith({ + Color? color, + FontWeight? fontWeight, + FontStyle? fontStyle, + double? fontSize, + RTFTextDecorationEnum? decoration, + List? fontFeatures, + TextAlign? alignment, + }) { + return RTFTextMetadata( + color: color ?? this.color, + fontWeight: fontWeight ?? this.fontWeight, + fontStyle: fontStyle ?? this.fontStyle, + fontSize: fontSize ?? this.fontSize, + decoration: decoration ?? this.decoration, + fontFeatures: fontFeatures ?? this.fontFeatures, + alignment: alignment ?? this.alignment, + ); + } + + /// This method is used to combine two `RTFTextMetadata` objects relative to the specified `TextMetadataChange` + RTFTextMetadata combineWhatChanged( + RTFTextMetadataChangeEnum change, + RTFTextMetadata other, + ) { + return switch (change) { + RTFTextMetadataChangeEnum.all => other, + RTFTextMetadataChangeEnum.color => copyWith(color: other.color), + RTFTextMetadataChangeEnum.fontWeight => + copyWith(fontWeight: other.fontWeight), + RTFTextMetadataChangeEnum.fontStyle => + copyWith(fontStyle: other.fontStyle), + RTFTextMetadataChangeEnum.fontSize => copyWith(fontSize: other.fontSize), + RTFTextMetadataChangeEnum.alignment => + copyWith(alignment: other.alignment), + RTFTextMetadataChangeEnum.fontDecoration => + copyWith(decoration: other.decoration), + RTFTextMetadataChangeEnum.fontFeatures => + copyWith(fontFeatures: other.fontFeatures), + }; + } + + TextStyle get style => TextStyle( + fontSize: fontSize, + color: color, + decoration: decoration.value, + fontWeight: fontWeight, + fontStyle: fontStyle, + fontFeatures: fontFeatures, + ); + + TextStyle get styleWithoutFontFeatures => TextStyle( + fontSize: fontSize, + color: color, + decoration: decoration.value, + fontWeight: fontWeight, + fontStyle: fontStyle, + ); + + /// `combineWhereNotEqual` is used to combine two `RTFTextMetadata` objects + factory RTFTextMetadata.combineWhereNotEqual( + final RTFTextMetadata firstMetadata, + final RTFTextMetadata secondMetaData, { + bool favourFirst = true, + }) { + return RTFTextMetadata( + color: favourFirst ? firstMetadata.color : secondMetaData.color, + fontWeight: + favourFirst ? firstMetadata.fontWeight : secondMetaData.fontWeight, + fontStyle: + favourFirst ? firstMetadata.fontStyle : secondMetaData.fontStyle, + fontSize: favourFirst ? firstMetadata.fontSize : secondMetaData.fontSize, + decoration: + favourFirst ? firstMetadata.decoration : secondMetaData.decoration, + fontFeatures: favourFirst + ? firstMetadata.fontFeatures + : secondMetaData.fontFeatures, + alignment: + favourFirst ? firstMetadata.alignment : secondMetaData.alignment, + ); + } + + RTFTextMetadata combineWith( + RTFTextMetadata other, { + bool favourOther = true, + }) { + return RTFTextMetadata( + color: color == other.color + ? color + : favourOther + ? other.color + : color, + fontWeight: fontWeight == other.fontWeight + ? fontWeight + : favourOther + ? other.fontWeight + : fontWeight, + fontStyle: fontStyle == other.fontStyle + ? fontStyle + : favourOther + ? other.fontStyle + : fontStyle, + fontSize: fontSize == other.fontSize + ? fontSize + : favourOther + ? other.fontSize + : fontSize, + decoration: decoration == other.decoration + ? decoration + : favourOther + ? other.decoration + : decoration, + fontFeatures: fontFeatures == other.fontFeatures + ? fontFeatures + : favourOther + ? other.fontFeatures ?? fontFeatures + : fontFeatures ?? other.fontFeatures, + alignment: alignment == other.alignment + ? alignment + : favourOther + ? other.alignment + : alignment, + ); + } + + factory RTFTextMetadata.fromMap(Map map) { + return RTFTextMetadata( + color: RTFConverter.colorFromMap(map), + fontWeight: FontWeight.values[(map['fontWeight'])], + fontStyle: FontStyle.values[(map['fontStyle'])], + fontSize: map['fontSize'] as double, + fontFeatures: (map['fontFeatures'] as List?) + ?.cast>() + .map((e) => _fontFeatureFromMap(e)) + .toList(), + alignment: TextAlign.values[(map['alignment'])], + decoration: RTFTextDecorationEnum.values[(map['decoration'])], + ); + } + + Map toMap() { + return { + 'color': color.toSerializerString, + 'fontWeight': fontWeight.index, + 'fontStyle': fontStyle.index, + 'fontSize': fontSize, + 'fontFeatures': fontFeatures?.map((e) => _fontFeatureToMap(e)).toList(), + 'alignment': alignment.index, + 'decoration': decoration.index, + }; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RTFTextMetadata && + runtimeType == other.runtimeType && + color == other.color && + fontWeight == other.fontWeight && + fontStyle == other.fontStyle && + fontSize == other.fontSize && + decoration == other.decoration && + fontFeatures == other.fontFeatures && + alignment == other.alignment; + + @override + int get hashCode => + color.hashCode ^ + fontWeight.hashCode ^ + fontStyle.hashCode ^ + fontSize.hashCode ^ + decoration.hashCode ^ + fontFeatures.hashCode ^ + alignment.hashCode; + + @override + String toString() { + return ''' +RTFTextMetadata{ + color: $color, + fontWeight: $fontWeight, + fontStyle: $fontStyle, + fontSize: $fontSize, + decoration: $decoration, + fontFeatures: $fontFeatures, + alignment: $alignment + }'''; + } +} + +/// `_fontFeatureFromMap` is a private function that converts a map to a `FontFeature` object +FontFeature _fontFeatureFromMap(Map map) { + return FontFeature( + map['feature'], + map['value'], + ); +} + +/// `_fontFeatureToMap` is a private function that converts a `FontFeature` object to a map +Map _fontFeatureToMap(FontFeature feature) { + return { + 'feature': feature.feature, + 'value': feature.value, + }; +} diff --git a/lib/src/core/utils/converter.dart b/lib/src/core/utils/converter.dart new file mode 100644 index 0000000..64e0fe4 --- /dev/null +++ b/lib/src/core/utils/converter.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart' as material; +import 'package:rtf_textfield/src/view/input_decoration/rtf_input_decorator.dart'; + +/// This is the base class for all RTF converters +abstract class RTFConverter { + /// Converts a `Map` to `Color` + static material.Color colorFromMap(dynamic map) { + return material.Color(int.parse(map['color'].toString())); + } + + /// Convert material's `FloatingLabelBehavior` to RTF's `FloatingLabelBehavior` + static FloatingLabelBehavior convertMaterial2FloatingLabelBehavior( + material.FloatingLabelBehavior behavior, + ) { + switch (behavior) { + case material.FloatingLabelBehavior.auto: + return FloatingLabelBehavior.auto; + case material.FloatingLabelBehavior.always: + return FloatingLabelBehavior.always; + case material.FloatingLabelBehavior.never: + return FloatingLabelBehavior.never; + } + } + + /// Convert material's `InputDecorationTheme` to RTF's `InputDecorationTheme` + static InputDecorationTheme convertMaterial2InputDecorationTheme( + material.InputDecorationTheme theme, + ) { + return InputDecorationTheme( + labelStyle: theme.labelStyle, + helperStyle: theme.helperStyle, + helperMaxLines: theme.helperMaxLines, + hintStyle: theme.hintStyle, + errorStyle: theme.errorStyle, + errorMaxLines: theme.errorMaxLines, + floatingLabelBehavior: convertMaterial2FloatingLabelBehavior( + theme.floatingLabelBehavior, + ), + isDense: theme.isDense, + contentPadding: theme.contentPadding, + isCollapsed: theme.isCollapsed, + prefixStyle: theme.prefixStyle, + suffixStyle: theme.suffixStyle, + counterStyle: theme.counterStyle, + filled: theme.filled, + fillColor: theme.fillColor, + focusColor: theme.focusColor, + hoverColor: theme.hoverColor, + errorBorder: theme.errorBorder, + focusedBorder: theme.focusedBorder, + focusedErrorBorder: theme.focusedErrorBorder, + disabledBorder: theme.disabledBorder, + enabledBorder: theme.enabledBorder, + border: theme.border, + alignLabelWithHint: theme.alignLabelWithHint, + ); + } +} diff --git a/lib/src/core/utils/enums/text_decoration.dart b/lib/src/core/utils/enums/text_decoration.dart new file mode 100644 index 0000000..6637d35 --- /dev/null +++ b/lib/src/core/utils/enums/text_decoration.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +///This super enum is used to convert `TextDecoration` to `RTFTextDecorationEnum` and vice versa. +/// It is being used to allow for easy data serialization. since `TextDecoration` does not expose `toJson()` and `fromJson()` methods. +enum RTFTextDecorationEnum { + none(TextDecoration.none, 'none'), + underline(TextDecoration.underline, 'underline'), + strikeThrough(TextDecoration.lineThrough, 'line-through'), + ; + + final TextDecoration value; + final String cssValue; + + const RTFTextDecorationEnum(this.value, this.cssValue); + + factory RTFTextDecorationEnum.fromDecoration(TextDecoration decoration) { + return values.firstWhere((element) => element.value == decoration); + } +} diff --git a/lib/src/core/utils/enums/text_metadata.dart b/lib/src/core/utils/enums/text_metadata.dart new file mode 100644 index 0000000..592077d --- /dev/null +++ b/lib/src/core/utils/enums/text_metadata.dart @@ -0,0 +1,12 @@ +/// Enum for text metadata change +/// It is used to specify which metadata field has changed +enum RTFTextMetadataChangeEnum { + all, + color, + fontWeight, + fontStyle, + fontSize, + alignment, + fontDecoration, + fontFeatures; +} diff --git a/lib/src/core/utils/extensions/color.dart b/lib/src/core/utils/extensions/color.dart new file mode 100644 index 0000000..5a62954 --- /dev/null +++ b/lib/src/core/utils/extensions/color.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +/// `RTFColorExtension` is an extension for `Color` +extension RTFColorExtension on Color { + /// `toSerializerString` returns the string representation of the color. + String get toSerializerString => value.toString(); +} diff --git a/lib/src/core/utils/extensions/iterable.dart b/lib/src/core/utils/extensions/iterable.dart new file mode 100644 index 0000000..6042b83 --- /dev/null +++ b/lib/src/core/utils/extensions/iterable.dart @@ -0,0 +1,13 @@ +/// `RTFIterableExtension` is a extension for `Iterable` class. +extension RTFIterableExtension on Iterable { + /// `containsWhere` returns `true` if the iterable contains an element + bool containsWhere(bool Function(E value) test) { + for (E element in this) { + bool satisfied = test(element); + if (satisfied) { + return true; + } + } + return false; + } +} diff --git a/lib/src/core/utils/extensions/list.dart b/lib/src/core/utils/extensions/list.dart new file mode 100644 index 0000000..3487a50 --- /dev/null +++ b/lib/src/core/utils/extensions/list.dart @@ -0,0 +1,47 @@ +/// `RTFListExtension` is a extension for `List` class +extension RTFListExtension on List { + /// `firstOrNull` returns the first element of the list or `null` if the list is empty. + E? get firstOrNull { + try { + return first; + } catch (e) { + return null; + } + } + + /// `lastOrNull` returns the last element of the list or `null` if the list is empty. + E? get lastOrNull { + try { + return last; + } catch (e) { + return null; + } + } + + /// `elementAtOrNull` returns the element at the given index or `null` if the index is out of bounds. + E? elementAtOrNull(int index) { + try { + return this[index]; + } catch (e) { + return null; + } + } + + /// `addItemBetweenList` adds an item at the given index. + void addItemBetweenList( + int index, { + required E item, + }) { + if (index > length) throw Exception('Index out of bounds'); + if (index == length) return add(item); + + final List completeList = [ + ...sublist(0, index), + item, + ...sublist(index, length), + ]; + + clear(); + addAll(completeList); + } +} diff --git a/lib/src/core/utils/extensions/string.dart b/lib/src/core/utils/extensions/string.dart new file mode 100644 index 0000000..99712fc --- /dev/null +++ b/lib/src/core/utils/extensions/string.dart @@ -0,0 +1,16 @@ +/// `RTFNullableStringExtension` is an extension for `String?` class. +extension RTFNullableStringExtension on String? { + /// Returns `true` if the string is `null` or empty. + bool get isNullOrEmpty => this?.isEmpty ?? true; +} + +/// `RTFStringExtension` is an extension for `String` class. +extension RTFStringExtension on String { + /// Removes all occurrences of the given [pattern] in the string. + String removeAll(String pattern) { + return replaceAll(pattern, ''); + } + + /// Returns a list of characters in the string. + List get chars => split(''); +} diff --git a/lib/src/core/utils/extensions/text_align.dart b/lib/src/core/utils/extensions/text_align.dart new file mode 100644 index 0000000..127d598 --- /dev/null +++ b/lib/src/core/utils/extensions/text_align.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +/// This extension is used to convert `TextAlign` to `Alignment` and vice versa. +extension RTFTextAlignExtension on TextAlign { + /// Converts `TextAlign` to `Alignment` + Alignment get toAlignment { + return switch (this) { + TextAlign.start || TextAlign.left => Alignment.centerLeft, + TextAlign.end || TextAlign.right => Alignment.centerRight, + TextAlign.center => Alignment.center, + TextAlign.justify => Alignment.center, + }; + } +} diff --git a/lib/src/core/utils/extensions/text_deltas.dart b/lib/src/core/utils/extensions/text_deltas.dart new file mode 100644 index 0000000..c35255c --- /dev/null +++ b/lib/src/core/utils/extensions/text_deltas.dart @@ -0,0 +1,28 @@ +import 'package:rtf_textfield/src/core/models/text_delta.dart'; + +/// Sugar for working with list of [TextDelta]s. +typedef TextDeltas = List; + +/// `RTFTextDeltasExtension` is an extension for `TextDeltas` class. +extension RTFTextDeltasExtension on TextDeltas { + /// Getter `text` return the string for given. Getter that retrieves a concatenated string from the elements of the collection. + String get text { + // If the collection is empty, immediately return an empty string. + if (isEmpty) return ''; + + // Initialize a StringBuffer with the character of the first element. + final StringBuffer stringBuffer = StringBuffer(first.char); + + // Iterate through the remaining elements starting from the second item. + for (int i = 1; i < length; i++) { + // Append the character of each element to the StringBuffer. + stringBuffer.write(this[i].char); + } + + // Convert the contents of the StringBuffer into a String. + return stringBuffer.toString(); + } + + /// Creates a value copy of the list + TextDeltas get copy => List.from(this); +} diff --git a/lib/src/core/utils/text_deltas.dart b/lib/src/core/utils/text_deltas.dart new file mode 100644 index 0000000..bbd7ed8 --- /dev/null +++ b/lib/src/core/utils/text_deltas.dart @@ -0,0 +1,30 @@ +import 'package:rtf_textfield/src/core/models/text_delta.dart'; +import 'package:rtf_textfield/src/core/models/text_metadata.dart'; +import 'package:rtf_textfield/src/core/utils/extensions/string.dart'; +import 'package:rtf_textfield/src/core/utils/extensions/text_deltas.dart'; + +/// `TextDeltasUtils` is a utility class that provides helper methods for `TextDeltas` +abstract class RTFTextDeltasUtils { + /// Converts a string to `TextDeltas` + static TextDeltas deltasFromString( + String string, [ + RTFTextMetadata? metadata, + ]) { + final TextDeltas deltas = []; + final List chars = string.chars; + + for (final String char in chars) { + deltas.add(RTFTextDelta(char: char, metadata: metadata)); + } + return deltas; + } + + /// Converts a list of `Map` to `TextDeltas` + static TextDeltas deltasFromList(List list) { + final TextDeltas deltas = []; + for (dynamic map in list) { + deltas.add(RTFTextDelta.fromMap((map as Map).cast())); + } + return deltas; + } +} diff --git a/lib/src/view/base/base_text_form_field.dart b/lib/src/view/base/base_text_form_field.dart new file mode 100644 index 0000000..cbcb7e7 --- /dev/null +++ b/lib/src/view/base/base_text_form_field.dart @@ -0,0 +1,403 @@ +import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' + hide InputCounterWidgetBuilder, InputDecorationTheme; +import 'package:flutter/services.dart'; +import 'package:rtf_textfield/rtf_textfield.dart'; + +export 'package:flutter/services.dart' show SmartDashesType, SmartQuotesType; + +/// A [FormField] that contains a [TextField]. +/// +/// This is a convenience widget that wraps a [TextField] widget in a +/// [FormField]. +/// +/// A [Form] ancestor is not required. The [Form] allows one to +/// save, reset, or validate multiple fields at once. To use without a [Form], +/// pass a `GlobalKey` (see [GlobalKey]) to the constructor and use +/// [GlobalKey.currentState] to save or reset the form field. +/// +/// When a [controller] is specified, its [TextEditingController.text] +/// defines the [initialValue]. If this [FormField] is part of a scrolling +/// container that lazily constructs its children, like a [ListView] or a +/// [CustomScrollView], then a [controller] should be specified. +/// The controller's lifetime should be managed by a stateful widget ancestor +/// of the scrolling container. +/// +/// If a [controller] is not specified, [initialValue] can be used to give +/// the automatically generated controller an initial value. +/// +/// {@macro flutter.material.textfield.wantKeepAlive} +/// +/// Remember to call [TextEditingController.dispose] of the [TextEditingController] +/// when it is no longer needed. This will ensure any resources used by the object +/// are discarded. +/// +/// By default, `decoration` will apply the [ThemeData.inputDecorationTheme] for +/// the current context to the [RichInputDecoration], see +/// [RichInputDecoration.applyDefaults]. +/// +/// For a documentation about the various parameters, see [TextField]. +/// +/// {@tool snippet} +/// +/// Creates a [RichTextFormField] with an [RichInputDecoration] and validator function. +/// +/// ![If the user enters valid text, the TextField appears normally without any warnings to the user](https://flutter.github.io/assets-for-api-docs/assets/material/text_form_field.png) +/// +/// ![If the user enters invalid text, the error message returned from the validator function is displayed in dark red underneath the input](https://flutter.github.io/assets-for-api-docs/assets/material/text_form_field_error.png) +/// +/// ```dart +/// RichTextFormField( +/// decoration: const RichInputDecoration( +/// icon: Icon(Icons.person), +/// hintText: 'What do people call you?', +/// labelText: 'Name *', +/// ), +/// onSaved: (String? value) { +/// // This optional block of code can be used to run +/// // code when the user saves the form. +/// }, +/// validator: (String? value) { +/// return (value != null && value.contains('@')) ? 'Do not use the @ char.' : null; +/// }, +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool dartpad} +/// This example shows how to move the focus to the next field when the user +/// presses the SPACE key. +/// +/// ** See code in examples/api/lib/material/text_form_field/text_form_field.1.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * +/// * [TextField], which is the underlying text field without the [Form] +/// integration. +/// * [RichInputDecorator], which shows the labels and other visual elements that +/// surround the actual text editing widget. +/// * Learn how to use a [TextEditingController] in one of our [cookbook recipes](https://flutter.dev/docs/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller). +class RichTextFormField extends FormField { + /// Creates a [FormField] that contains a [TextField]. + /// + /// When a [controller] is specified, [initialValue] must be null (the + /// default). If [controller] is null, then a [TextEditingController] + /// will be constructed automatically and its `text` will be initialized + /// to [initialValue] or the empty string. + /// + /// For documentation about the various parameters, see the [TextField] class + /// and [TextField.new], the constructor. + RichTextFormField({ + super.key, + this.controller, + String? initialValue, + FocusNode? focusNode, + RichInputDecoration? decoration = const RichInputDecoration(), + TextInputType? keyboardType, + TextCapitalization textCapitalization = TextCapitalization.none, + TextInputAction? textInputAction, + TextStyle? style, + StrutStyle? strutStyle, + TextDirection? textDirection, + TextAlign textAlign = TextAlign.start, + TextAlignVertical? textAlignVertical, + bool autofocus = false, + bool readOnly = false, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + ToolbarOptions? toolbarOptions, + bool? showCursor, + String obscuringCharacter = '•', + bool obscureText = false, + bool autocorrect = true, + SmartDashesType? smartDashesType, + SmartQuotesType? smartQuotesType, + bool enableSuggestions = true, + MaxLengthEnforcement? maxLengthEnforcement, + int? maxLines = 1, + int? minLines, + bool expands = false, + int? maxLength, + this.onChanged, + GestureTapCallback? onTap, + TapRegionCallback? onTapOutside, + VoidCallback? onEditingComplete, + ValueChanged? onFieldSubmitted, + super.onSaved, + super.validator, + List? inputFormatters, + bool? enabled, + double cursorWidth = 2.0, + double? cursorHeight, + Radius? cursorRadius, + Color? cursorColor, + Brightness? keyboardAppearance, + EdgeInsets scrollPadding = const EdgeInsets.all(20.0), + bool? enableInteractiveSelection, + TextSelectionControls? selectionControls, + InputCounterWidgetBuilder? buildCounters, + ScrollPhysics? scrollPhysics, + Iterable? autofillHints, + AutovalidateMode? autovalidateMode, + ScrollController? scrollController, + super.restorationId, + bool enableIMEPersonalizedLearning = true, + MouseCursor? mouseCursor, + EditableTextContextMenuBuilder? contextMenuBuilder = + _defaultContextMenuBuilder, + SpellCheckConfiguration? spellCheckConfiguration, + TextMagnifierConfiguration? magnifierConfiguration, + UndoHistoryController? undoController, + AppPrivateCommandCallback? onAppPrivateCommand, + bool? cursorOpacityAnimates, + ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.tight, + ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.tight, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, + ContentInsertionConfiguration? contentInsertionConfiguration, + Clip clipBehavior = Clip.hardEdge, + bool scribbleEnabled = true, + bool canRequestFocus = true, + }) : assert(initialValue == null || controller == null), + assert(obscuringCharacter.length == 1), + assert(maxLines == null || maxLines > 0), + assert(minLines == null || minLines > 0), + assert( + (maxLines == null) || (minLines == null) || (maxLines >= minLines), + "minLines can't be greater than maxLines", + ), + assert( + !expands || (maxLines == null && minLines == null), + 'minLines and maxLines must be null when expands is true.', + ), + assert(!obscureText || maxLines == 1, + 'Obscured fields cannot be multiline.'), + assert(maxLength == null || + maxLength == TextField.noMaxLength || + maxLength > 0), + super( + initialValue: + controller != null ? controller.text : (initialValue ?? ''), + enabled: enabled ?? decoration?.enabled ?? true, + autovalidateMode: autovalidateMode ?? AutovalidateMode.disabled, + builder: (FormFieldState field) { + final _TextFormFieldState state = field as _TextFormFieldState; + final RichInputDecoration effectiveDecoration = + (decoration ?? const RichInputDecoration()).applyDefaults( + Theme.of(field.context).inputDecorationTheme + as InputDecorationTheme); + void onChangedHandler(String value) { + field.didChange(value); + onChanged?.call(value); + } + + return UnmanagedRestorationScope( + bucket: field.bucket, + child: RichTextField( + restorationId: restorationId, + controller: state._effectiveController, + focusNode: focusNode, + decoration: + effectiveDecoration.copyWith(errorText: field.errorText), + keyboardType: keyboardType, + textInputAction: textInputAction, + style: style, + strutStyle: strutStyle, + textAlign: textAlign, + textAlignVertical: textAlignVertical, + textDirection: textDirection, + textCapitalization: textCapitalization, + autofocus: autofocus, + readOnly: readOnly, + showCursor: showCursor, + obscuringCharacter: obscuringCharacter, + obscureText: obscureText, + autocorrect: autocorrect, + smartDashesType: smartDashesType ?? + (obscureText + ? SmartDashesType.disabled + : SmartDashesType.enabled), + smartQuotesType: smartQuotesType ?? + (obscureText + ? SmartQuotesType.disabled + : SmartQuotesType.enabled), + enableSuggestions: enableSuggestions, + maxLengthEnforcement: maxLengthEnforcement, + maxLines: maxLines, + minLines: minLines, + expands: expands, + maxLength: maxLength, + onChanged: onChangedHandler, + onTap: onTap, + onTapOutside: onTapOutside, + onEditingComplete: onEditingComplete, + onSubmitted: onFieldSubmitted, + inputFormatters: inputFormatters, + enabled: enabled ?? decoration?.enabled ?? true, + cursorWidth: cursorWidth, + cursorHeight: cursorHeight, + cursorRadius: cursorRadius, + cursorColor: cursorColor, + scrollPadding: scrollPadding, + scrollPhysics: scrollPhysics, + keyboardAppearance: keyboardAppearance, + enableInteractiveSelection: + enableInteractiveSelection ?? (!obscureText || !readOnly), + selectionControls: selectionControls, + buildCounter: buildCounters, + autofillHints: autofillHints, + scrollController: scrollController, + enableIMEPersonalizedLearning: enableIMEPersonalizedLearning, + mouseCursor: mouseCursor, + contextMenuBuilder: contextMenuBuilder, + spellCheckConfiguration: spellCheckConfiguration, + magnifierConfiguration: magnifierConfiguration, + undoController: undoController, + onAppPrivateCommand: onAppPrivateCommand, + cursorOpacityAnimates: cursorOpacityAnimates, + selectionHeightStyle: selectionHeightStyle, + selectionWidthStyle: selectionWidthStyle, + dragStartBehavior: dragStartBehavior, + contentInsertionConfiguration: contentInsertionConfiguration, + clipBehavior: clipBehavior, + scribbleEnabled: scribbleEnabled, + canRequestFocus: canRequestFocus, + ), + ); + }, + ); + + /// Controls the text being edited. + /// + /// If null, this widget will create its own [TextEditingController] and + /// initialize its [TextEditingController.text] with [initialValue]. + final TextEditingController? controller; + + /// {@template flutter.material.RichTextFormField.onChanged} + /// Called when the user initiates a change to the TextField's + /// value: when they have inserted or deleted text or reset the form. + /// {@endtemplate} + final ValueChanged? onChanged; + + static Widget _defaultContextMenuBuilder( + BuildContext context, EditableTextState editableTextState) { + return AdaptiveTextSelectionToolbar.editableText( + editableTextState: editableTextState, + ); + } + + @override + FormFieldState createState() => _TextFormFieldState(); +} + +class _TextFormFieldState extends FormFieldState { + RestorableTextEditingController? _controller; + + TextEditingController get _effectiveController => + _textFormField.controller ?? _controller!.value; + + RichTextFormField get _textFormField => super.widget as RichTextFormField; + + @override + void restoreState(RestorationBucket? oldBucket, bool initialRestore) { + super.restoreState(oldBucket, initialRestore); + if (_controller != null) { + _registerController(); + } + // Make sure to update the internal [FormFieldState] value to sync up with + // text editing controller value. + setValue(_effectiveController.text); + } + + void _registerController() { + assert(_controller != null); + registerForRestoration(_controller!, 'controller'); + } + + void _createLocalController([TextEditingValue? value]) { + assert(_controller == null); + _controller = value == null + ? RestorableTextEditingController() + : RestorableTextEditingController.fromValue(value); + if (!restorePending) { + _registerController(); + } + } + + @override + void initState() { + super.initState(); + if (_textFormField.controller == null) { + _createLocalController(widget.initialValue != null + ? TextEditingValue(text: widget.initialValue!) + : null); + } else { + _textFormField.controller!.addListener(_handleControllerChanged); + } + } + + @override + void didUpdateWidget(RichTextFormField oldWidget) { + super.didUpdateWidget(oldWidget); + if (_textFormField.controller != oldWidget.controller) { + oldWidget.controller?.removeListener(_handleControllerChanged); + _textFormField.controller?.addListener(_handleControllerChanged); + + if (oldWidget.controller != null && _textFormField.controller == null) { + _createLocalController(oldWidget.controller!.value); + } + + if (_textFormField.controller != null) { + setValue(_textFormField.controller!.text); + if (oldWidget.controller == null) { + unregisterFromRestoration(_controller!); + _controller!.dispose(); + _controller = null; + } + } + } + } + + @override + void dispose() { + _textFormField.controller?.removeListener(_handleControllerChanged); + _controller?.dispose(); + super.dispose(); + } + + @override + void didChange(String? value) { + super.didChange(value); + + if (_effectiveController.text != value) { + _effectiveController.text = value ?? ''; + } + } + + @override + void reset() { + // Set the controller value before calling super.reset() to let + // _handleControllerChanged suppress the change. + _effectiveController.text = widget.initialValue ?? ''; + super.reset(); + _textFormField.onChanged?.call(_effectiveController.text); + } + + void _handleControllerChanged() { + // Suppress changes that originated from within this class. + // + // In the case where a controller has been passed in to this widget, we + // register this change listener. In these cases, we'll also receive change + // notifications for changes originating from within this class -- for + // example, the reset() method. In such cases, the FormField value will + // already have been set. + if (_effectiveController.text != value) { + didChange(_effectiveController.text); + } + } +} diff --git a/lib/src/view/base/base_textfield.dart b/lib/src/view/base/base_textfield.dart new file mode 100644 index 0000000..2900b26 --- /dev/null +++ b/lib/src/view/base/base_textfield.dart @@ -0,0 +1,1707 @@ +import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' hide InputDecorationTheme; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:rtf_textfield/rtf_textfield.dart'; +import 'package:rtf_textfield/src/core/utils/converter.dart'; + +export 'package:flutter/services.dart' + show + SmartDashesType, + SmartQuotesType, + TextCapitalization, + TextInputAction, + TextInputType; + +// Examples can assume: +// late BuildContext context; +// late FocusNode myFocusNode; + +/// Signature for the [RichTextField.buildCounter] callback. +typedef InputCounterWidgetBuilder = Widget? Function( + /// The build context for the RichTextField. + BuildContext context, { + /// The length of the string currently in the input. + required int currentLength, + + /// The maximum string length that can be entered into the RichTextField. + required int? maxLength, + + /// Whether or not the RichTextField is currently focused. Mainly provided for + /// the [liveRegion] parameter in the [Semantics] widget for accessibility. + required bool isFocused, +}); + +class _TextFieldSelectionGestureDetectorBuilder + extends TextSelectionGestureDetectorBuilder { + _TextFieldSelectionGestureDetectorBuilder({ + required _TextFieldState state, + }) : _state = state, + super(delegate: state); + + final _TextFieldState _state; + + @override + void onForcePressStart(ForcePressDetails details) { + super.onForcePressStart(details); + if (delegate.selectionEnabled && shouldShowSelectionToolbar) { + editableText.showToolbar(); + } + } + + @override + void onForcePressEnd(ForcePressDetails details) { + // Not required. + } + + @override + void onSingleTapUp(TapDragUpDetails details) { + super.onSingleTapUp(details); + _state._requestKeyboard(); + _state.widget.onTap?.call(); + } + + @override + void onSingleLongTapStart(LongPressStartDetails details) { + super.onSingleLongTapStart(details); + if (delegate.selectionEnabled) { + switch (Theme.of(_state.context).platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + Feedback.forLongPress(_state.context); + } + } + } +} + +/// A Material Design text field. +/// +/// A text field lets the user enter text, either with hardware keyboard or with +/// an onscreen keyboard. +/// +/// The text field calls the [onChanged] callback whenever the user changes the +/// text in the field. If the user indicates that they are done typing in the +/// field (e.g., by pressing a button on the soft keyboard), the text field +/// calls the [onSubmitted] callback. +/// +/// To control the text that is displayed in the text field, use the +/// [controller]. For example, to set the initial value of the text field, use +/// a [controller] that already contains some text. The [controller] can also +/// control the selection and composing region (and to observe changes to the +/// text, selection, and composing region). +/// +/// By default, a text field has a [decoration] that draws a divider below the +/// text field. You can use the [decoration] property to control the decoration, +/// for example by adding a label or an icon. If you set the [decoration] +/// property to null, the decoration will be removed entirely, including the +/// extra padding introduced by the decoration to save space for the labels. +/// +/// If [decoration] is non-null (which is the default), the text field requires +/// one of its ancestors to be a [Material] widget. +/// +/// To integrate the [RichTextField] into a [Form] with other [FormField] widgets, +/// consider using [TextFormField]. +/// +/// {@template flutter.material.textfield.wantKeepAlive} +/// When the widget has focus, it will prevent itself from disposing via its +/// underlying [EditableText]'s [AutomaticKeepAliveClientMixin.wantKeepAlive] in +/// order to avoid losing the selection. Removing the focus will allow it to be +/// disposed. +/// {@endtemplate} +/// +/// Remember to call [TextEditingController.dispose] of the [TextEditingController] +/// when it is no longer needed. This will ensure we discard any resources used +/// by the object. +/// +/// ## Obscured Input +/// +/// {@tool dartpad} +/// This example shows how to create a [RichTextField] that will obscure input. The +/// [RichInputDecoration] surrounds the field in a border using [OutlineInputBorder] +/// and adds a label. +/// +/// ** See code in examples/api/lib/material/text_field/text_field.0.dart ** +/// {@end-tool} +/// +/// ## Reading values +/// +/// A common way to read a value from a RichTextField is to use the [onSubmitted] +/// callback. This callback is applied to the text field's current value when +/// the user finishes editing. +/// +/// {@tool dartpad} +/// This sample shows how to get a value from a RichTextField via the [onSubmitted] +/// callback. +/// +/// ** See code in examples/api/lib/material/text_field/text_field.1.dart ** +/// {@end-tool} +/// +/// {@macro flutter.widgets.EditableText.lifeCycle} +/// +/// For most applications the [onSubmitted] callback will be sufficient for +/// reacting to user input. +/// +/// The [onEditingComplete] callback also runs when the user finishes editing. +/// It's different from [onSubmitted] because it has a default value which +/// updates the text controller and yields the keyboard focus. Applications that +/// require different behavior can override the default [onEditingComplete] +/// callback. +/// +/// Keep in mind you can also always read the current string from a RichTextField's +/// [TextEditingController] using [TextEditingController.text]. +/// +/// ## Handling emojis and other complex characters +/// {@macro flutter.widgets.EditableText.onChanged} +/// +/// In the live Dartpad example above, try typing the emoji 👨‍👩‍👦 +/// into the field and submitting. Because the example code measures the length +/// with `value.characters.length`, the emoji is correctly counted as a single +/// character. +/// +/// {@macro flutter.widgets.editableText.showCaretOnScreen} +/// +/// {@macro flutter.widgets.editableText.accessibility} +/// +/// {@tool dartpad} +/// This sample shows how to style a text field to match a filled or outlined +/// Material Design 3 text field. +/// +/// ** See code in examples/api/lib/material/text_field/text_field.2.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * [TextFormField], which integrates with the [Form] widget. +/// * [RichInputDecorator], which shows the labels and other visual elements that +/// surround the actual text editing widget. +/// * [EditableText], which is the raw text editing control at the heart of a +/// [RichTextField]. The [EditableText] widget is rarely used directly unless +/// you are implementing an entirely different design language, such as +/// Cupertino. +/// * +/// * Cookbook: [Create and style a text field](https://flutter.dev/docs/cookbook/forms/text-input) +/// * Cookbook: [Handle changes to a text field](https://flutter.dev/docs/cookbook/forms/text-field-changes) +/// * Cookbook: [Retrieve the value of a text field](https://flutter.dev/docs/cookbook/forms/retrieve-input) +/// * Cookbook: [Focus and text fields](https://flutter.dev/docs/cookbook/forms/focus) +class RichTextField extends StatefulWidget { + /// Creates a Material Design text field. + /// + /// If [decoration] is non-null (which is the default), the text field requires + /// one of its ancestors to be a [Material] widget. + /// + /// To remove the decoration entirely (including the extra padding introduced + /// by the decoration to save space for the labels), set the [decoration] to + /// null. + /// + /// The [maxLines] property can be set to null to remove the restriction on + /// the number of lines. By default, it is one, meaning this is a single-line + /// text field. [maxLines] must not be zero. + /// + /// The [maxLength] property is set to null by default, which means the + /// number of characters allowed in the text field is not restricted. If + /// [maxLength] is set a character counter will be displayed below the + /// field showing how many characters have been entered. If the value is + /// set to a positive integer it will also display the maximum allowed + /// number of characters to be entered. If the value is set to + /// [RichTextField.noMaxLength] then only the current length is displayed. + /// + /// After [maxLength] characters have been input, additional input + /// is ignored, unless [maxLengthEnforcement] is set to + /// [MaxLengthEnforcement.none]. + /// The text field enforces the length with a [LengthLimitingTextInputFormatter], + /// which is evaluated after the supplied [inputFormatters], if any. + /// The [maxLength] value must be either null or greater than zero. + /// + /// If [maxLengthEnforcement] is set to [MaxLengthEnforcement.none], then more + /// than [maxLength] characters may be entered, and the error counter and + /// divider will switch to the [decoration].errorStyle when the limit is + /// exceeded. + /// + /// The text cursor is not shown if [showCursor] is false or if [showCursor] + /// is null (the default) and [readOnly] is true. + /// + /// The [selectionHeightStyle] and [selectionWidthStyle] properties allow + /// changing the shape of the selection highlighting. These properties default + /// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight], respectively. + /// + /// See also: + /// + /// * [maxLength], which discusses the precise meaning of "number of + /// characters" and how it may differ from the intuitive meaning. + const RichTextField({ + super.key, + this.controller, + this.focusNode, + this.undoController, + this.decoration = const RichInputDecoration(), + TextInputType? keyboardType, + this.textInputAction, + this.textCapitalization = TextCapitalization.none, + this.style, + this.strutStyle, + this.textAlign = TextAlign.start, + this.textAlignVertical, + this.textDirection, + this.readOnly = false, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + this.toolbarOptions, + this.showCursor, + this.autofocus = false, + this.obscuringCharacter = '•', + this.obscureText = false, + this.autocorrect = true, + SmartDashesType? smartDashesType, + SmartQuotesType? smartQuotesType, + this.enableSuggestions = true, + this.maxLines = 1, + this.minLines, + this.expands = false, + this.maxLength, + this.maxLengthEnforcement, + this.onChanged, + this.onEditingComplete, + this.onSubmitted, + this.onAppPrivateCommand, + this.inputFormatters, + this.enabled, + this.cursorWidth = 2.0, + this.cursorHeight, + this.cursorRadius, + this.cursorOpacityAnimates, + this.cursorColor, + this.selectionHeightStyle = ui.BoxHeightStyle.tight, + this.selectionWidthStyle = ui.BoxWidthStyle.tight, + this.keyboardAppearance, + this.scrollPadding = const EdgeInsets.all(20.0), + this.dragStartBehavior = DragStartBehavior.start, + bool? enableInteractiveSelection, + this.selectionControls, + this.onTap, + this.onTapOutside, + this.mouseCursor, + this.buildCounter, + this.scrollController, + this.scrollPhysics, + this.autofillHints = const [], + this.contentInsertionConfiguration, + this.clipBehavior = Clip.hardEdge, + this.restorationId, + this.scribbleEnabled = true, + this.enableIMEPersonalizedLearning = true, + this.contextMenuBuilder = _defaultContextMenuBuilder, + this.canRequestFocus = true, + this.spellCheckConfiguration, + this.magnifierConfiguration, + }) : assert(obscuringCharacter.length == 1), + smartDashesType = smartDashesType ?? + (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), + smartQuotesType = smartQuotesType ?? + (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), + assert(maxLines == null || maxLines > 0), + assert(minLines == null || minLines > 0), + assert( + (maxLines == null) || (minLines == null) || (maxLines >= minLines), + "minLines can't be greater than maxLines", + ), + assert( + !expands || (maxLines == null && minLines == null), + 'minLines and maxLines must be null when expands is true.', + ), + assert(!obscureText || maxLines == 1, + 'Obscured fields cannot be multiline.'), + assert(maxLength == null || + maxLength == RichTextField.noMaxLength || + maxLength > 0), + // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set. + assert( + !identical(textInputAction, TextInputAction.newline) || + maxLines == 1 || + !identical(keyboardType, TextInputType.text), + 'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline RichTextField.', + ), + keyboardType = keyboardType ?? + (maxLines == 1 ? TextInputType.text : TextInputType.multiline), + enableInteractiveSelection = + enableInteractiveSelection ?? (!readOnly || !obscureText); + + /// {@macro flutter.widgets.magnifier.TextMagnifierConfiguration.intro} + /// + /// {@macro flutter.widgets.magnifier.intro} + /// + /// {@macro flutter.widgets.magnifier.TextMagnifierConfiguration.details} + /// + /// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier] + /// on Android, and builds nothing on all other platforms. If it is desired to + /// suppress the magnifier, consider passing [TextMagnifierConfiguration.disabled]. + /// + /// {@tool dartpad} + /// This sample demonstrates how to customize the magnifier that this text field uses. + /// + /// ** See code in examples/api/lib/widgets/text_magnifier/text_magnifier.0.dart ** + /// {@end-tool} + final TextMagnifierConfiguration? magnifierConfiguration; + + /// Controls the text being edited. + /// + /// If null, this widget will create its own [TextEditingController]. + final TextEditingController? controller; + + /// Defines the keyboard focus for this widget. + /// + /// The [focusNode] is a long-lived object that's typically managed by a + /// [StatefulWidget] parent. See [FocusNode] for more information. + /// + /// To give the keyboard focus to this widget, provide a [focusNode] and then + /// use the current [FocusScope] to request the focus: + /// + /// ```dart + /// FocusScope.of(context).requestFocus(myFocusNode); + /// ``` + /// + /// This happens automatically when the widget is tapped. + /// + /// To be notified when the widget gains or loses the focus, add a listener + /// to the [focusNode]: + /// + /// ```dart + /// myFocusNode.addListener(() { print(myFocusNode.hasFocus); }); + /// ``` + /// + /// If null, this widget will create its own [FocusNode]. + /// + /// ## Keyboard + /// + /// Requesting the focus will typically cause the keyboard to be shown + /// if it's not showing already. + /// + /// On Android, the user can hide the keyboard - without changing the focus - + /// with the system back button. They can restore the keyboard's visibility + /// by tapping on a text field. The user might hide the keyboard and + /// switch to a physical keyboard, or they might just need to get it + /// out of the way for a moment, to expose something it's + /// obscuring. In this case requesting the focus again will not + /// cause the focus to change, and will not make the keyboard visible. + /// + /// This widget builds an [EditableText] and will ensure that the keyboard is + /// showing when it is tapped by calling [EditableTextState.requestKeyboard()]. + final FocusNode? focusNode; + + /// The decoration to show around the text field. + /// + /// By default, draws a horizontal line under the text field but can be + /// configured to show an icon, label, hint text, and error text. + /// + /// Specify null to remove the decoration entirely (including the + /// extra padding introduced by the decoration to save space for the labels). + final RichInputDecoration? decoration; + + /// {@macro flutter.widgets.editableText.keyboardType} + final TextInputType keyboardType; + + /// The type of action button to use for the keyboard. + /// + /// Defaults to [TextInputAction.newline] if [keyboardType] is + /// [TextInputType.multiline] and [TextInputAction.done] otherwise. + final TextInputAction? textInputAction; + + /// {@macro flutter.widgets.editableText.textCapitalization} + final TextCapitalization textCapitalization; + + /// The style to use for the text being edited. + /// + /// This text style is also used as the base style for the [decoration]. + /// + /// If null, [TextTheme.bodyLarge] will be used. When the text field is disabled, + /// [TextTheme.bodyLarge] with an opacity of 0.38 will be used instead. + /// + /// If null and [ThemeData.useMaterial3] is false, [TextTheme.titleMedium] will + /// be used. When the text field is disabled, [TextTheme.titleMedium] with + /// [ThemeData.disabledColor] will be used instead. + final TextStyle? style; + + /// {@macro flutter.widgets.editableText.strutStyle} + final StrutStyle? strutStyle; + + /// {@macro flutter.widgets.editableText.textAlign} + final TextAlign textAlign; + + /// {@macro flutter.material.InputDecorator.textAlignVertical} + final TextAlignVertical? textAlignVertical; + + /// {@macro flutter.widgets.editableText.textDirection} + final TextDirection? textDirection; + + /// {@macro flutter.widgets.editableText.autofocus} + final bool autofocus; + + /// {@macro flutter.widgets.editableText.obscuringCharacter} + final String obscuringCharacter; + + /// {@macro flutter.widgets.editableText.obscureText} + final bool obscureText; + + /// {@macro flutter.widgets.editableText.autocorrect} + final bool autocorrect; + + /// {@macro flutter.services.TextInputConfiguration.smartDashesType} + final SmartDashesType smartDashesType; + + /// {@macro flutter.services.TextInputConfiguration.smartQuotesType} + final SmartQuotesType smartQuotesType; + + /// {@macro flutter.services.TextInputConfiguration.enableSuggestions} + final bool enableSuggestions; + + /// {@macro flutter.widgets.editableText.maxLines} + /// * [expands], which determines whether the field should fill the height of + /// its parent. + final int? maxLines; + + /// {@macro flutter.widgets.editableText.minLines} + /// * [expands], which determines whether the field should fill the height of + /// its parent. + final int? minLines; + + /// {@macro flutter.widgets.editableText.expands} + final bool expands; + + /// {@macro flutter.widgets.editableText.readOnly} + final bool readOnly; + + /// Configuration of toolbar options. + /// + /// If not set, select all and paste will default to be enabled. Copy and cut + /// will be disabled if [obscureText] is true. If [readOnly] is true, + /// paste and cut will be disabled regardless. + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + final ToolbarOptions? toolbarOptions; + + /// {@macro flutter.widgets.editableText.showCursor} + final bool? showCursor; + + /// If [maxLength] is set to this value, only the "current input length" + /// part of the character counter is shown. + static const int noMaxLength = -1; + + /// The maximum number of characters (Unicode grapheme clusters) to allow in + /// the text field. + /// + /// If set, a character counter will be displayed below the + /// field showing how many characters have been entered. If set to a number + /// greater than 0, it will also display the maximum number allowed. If set + /// to [RichTextField.noMaxLength] then only the current character count is displayed. + /// + /// After [maxLength] characters have been input, additional input + /// is ignored, unless [maxLengthEnforcement] is set to + /// [MaxLengthEnforcement.none]. + /// + /// The text field enforces the length with a [LengthLimitingTextInputFormatter], + /// which is evaluated after the supplied [inputFormatters], if any. + /// + /// This value must be either null, [RichTextField.noMaxLength], or greater than 0. + /// If null (the default) then there is no limit to the number of characters + /// that can be entered. If set to [RichTextField.noMaxLength], then no limit will + /// be enforced, but the number of characters entered will still be displayed. + /// + /// Whitespace characters (e.g. newline, space, tab) are included in the + /// character count. + /// + /// If [maxLengthEnforcement] is [MaxLengthEnforcement.none], then more than + /// [maxLength] characters may be entered, but the error counter and divider + /// will switch to the [decoration]'s [RichInputDecoration.errorStyle] when the + /// limit is exceeded. + /// + /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength} + final int? maxLength; + + /// Determines how the [maxLength] limit should be enforced. + /// + /// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement} + /// + /// {@macro flutter.services.textFormatter.maxLengthEnforcement} + final MaxLengthEnforcement? maxLengthEnforcement; + + /// {@macro flutter.widgets.editableText.onChanged} + /// + /// See also: + /// + /// * [inputFormatters], which are called before [onChanged] + /// runs and can validate and change ("format") the input value. + /// * [onEditingComplete], [onSubmitted]: + /// which are more specialized input change notifications. + final ValueChanged? onChanged; + + /// {@macro flutter.widgets.editableText.onEditingComplete} + final VoidCallback? onEditingComplete; + + /// {@macro flutter.widgets.editableText.onSubmitted} + /// + /// See also: + /// + /// * [TextInputAction.next] and [TextInputAction.previous], which + /// automatically shift the focus to the next/previous focusable item when + /// the user is done editing. + final ValueChanged? onSubmitted; + + /// {@macro flutter.widgets.editableText.onAppPrivateCommand} + final AppPrivateCommandCallback? onAppPrivateCommand; + + /// {@macro flutter.widgets.editableText.inputFormatters} + final List? inputFormatters; + + /// If false the text field is "disabled": it ignores taps and its + /// [decoration] is rendered in grey. + /// + /// If non-null this property overrides the [decoration]'s + /// [RichInputDecoration.enabled] property. + final bool? enabled; + + /// {@macro flutter.widgets.editableText.cursorWidth} + final double cursorWidth; + + /// {@macro flutter.widgets.editableText.cursorHeight} + final double? cursorHeight; + + /// {@macro flutter.widgets.editableText.cursorRadius} + final Radius? cursorRadius; + + /// {@macro flutter.widgets.editableText.cursorOpacityAnimates} + final bool? cursorOpacityAnimates; + + /// The color of the cursor. + /// + /// The cursor indicates the current location of text insertion point in + /// the field. + /// + /// If this is null it will default to the ambient + /// [DefaultSelectionStyle.cursorColor]. If that is null, and the + /// [ThemeData.platform] is [TargetPlatform.iOS] or [TargetPlatform.macOS] + /// it will use [CupertinoThemeData.primaryColor]. Otherwise it will use + /// the value of [ColorScheme.primary] of [ThemeData.colorScheme]. + final Color? cursorColor; + + /// Controls how tall the selection highlight boxes are computed to be. + /// + /// See [ui.BoxHeightStyle] for details on available styles. + final ui.BoxHeightStyle selectionHeightStyle; + + /// Controls how wide the selection highlight boxes are computed to be. + /// + /// See [ui.BoxWidthStyle] for details on available styles. + final ui.BoxWidthStyle selectionWidthStyle; + + /// The appearance of the keyboard. + /// + /// This setting is only honored on iOS devices. + /// + /// If unset, defaults to [ThemeData.brightness]. + final Brightness? keyboardAppearance; + + /// {@macro flutter.widgets.editableText.scrollPadding} + final EdgeInsets scrollPadding; + + /// {@macro flutter.widgets.editableText.enableInteractiveSelection} + final bool enableInteractiveSelection; + + /// {@macro flutter.widgets.editableText.selectionControls} + final TextSelectionControls? selectionControls; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// {@macro flutter.widgets.editableText.selectionEnabled} + bool get selectionEnabled => enableInteractiveSelection; + + /// {@template flutter.material.textfield.onTap} + /// Called for each distinct tap except for every second tap of a double tap. + /// + /// The text field builds a [GestureDetector] to handle input events like tap, + /// to trigger focus requests, to move the caret, adjust the selection, etc. + /// Handling some of those events by wrapping the text field with a competing + /// GestureDetector is problematic. + /// + /// To unconditionally handle taps, without interfering with the text field's + /// internal gesture detector, provide this callback. + /// + /// If the text field is created with [enabled] false, taps will not be + /// recognized. + /// + /// To be notified when the text field gains or loses the focus, provide a + /// [focusNode] and add a listener to that. + /// + /// To listen to arbitrary pointer events without competing with the + /// text field's internal gesture detector, use a [Listener]. + /// {@endtemplate} + final GestureTapCallback? onTap; + + /// {@macro flutter.widgets.editableText.onTapOutside} + /// + /// {@tool dartpad} + /// This example shows how to use a `TextFieldTapRegion` to wrap a set of + /// "spinner" buttons that increment and decrement a value in the [RichTextField] + /// without causing the text field to lose keyboard focus. + /// + /// This example includes a generic `SpinnerField` class that you can copy + /// into your own project and customize. + /// + /// ** See code in examples/api/lib/widgets/tap_region/text_field_tap_region.0.dart ** + /// {@end-tool} + /// + /// See also: + /// + /// * [TapRegion] for how the region group is determined. + final TapRegionCallback? onTapOutside; + + /// The cursor for a mouse pointer when it enters or is hovering over the + /// widget. + /// + /// If [mouseCursor] is a [MaterialStateProperty], + /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s: + /// + /// * [MaterialState.error]. + /// * [MaterialState.hovered]. + /// * [MaterialState.focused]. + /// * [MaterialState.disabled]. + /// + /// If this property is null, [MaterialStateMouseCursor.textable] will be used. + /// + /// The [mouseCursor] is the only property of [RichTextField] that controls the + /// appearance of the mouse pointer. All other properties related to "cursor" + /// stand for the text cursor, which is usually a blinking vertical line at + /// the editing position. + final MouseCursor? mouseCursor; + + /// Callback that generates a custom [RichInputDecoration.counter] widget. + /// + /// See [InputCounterWidgetBuilder] for an explanation of the passed in + /// arguments. The returned widget will be placed below the line in place of + /// the default widget built when [RichInputDecoration.counterText] is specified. + /// + /// The returned widget will be wrapped in a [Semantics] widget for + /// accessibility, but it also needs to be accessible itself. For example, + /// if returning a Text widget, set the [Text.semanticsLabel] property. + /// + /// {@tool snippet} + /// ```dart + /// Widget counter( + /// BuildContext context, + /// { + /// required int currentLength, + /// required int? maxLength, + /// required bool isFocused, + /// } + /// ) { + /// return Text( + /// '$currentLength of $maxLength characters', + /// semanticsLabel: 'character count', + /// ); + /// } + /// ``` + /// {@end-tool} + /// + /// If buildCounter returns null, then no counter and no Semantics widget will + /// be created at all. + final InputCounterWidgetBuilder? buildCounter; + + /// {@macro flutter.widgets.editableText.scrollPhysics} + final ScrollPhysics? scrollPhysics; + + /// {@macro flutter.widgets.editableText.scrollController} + final ScrollController? scrollController; + + /// {@macro flutter.widgets.editableText.autofillHints} + /// {@macro flutter.services.AutofillConfiguration.autofillHints} + final Iterable? autofillHints; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + /// {@template flutter.material.textfield.restorationId} + /// Restoration ID to save and restore the state of the text field. + /// + /// If non-null, the text field will persist and restore its current scroll + /// offset and - if no [controller] has been provided - the content of the + /// text field. If a [controller] has been provided, it is the responsibility + /// of the owner of that controller to persist and restore it, e.g. by using + /// a [RestorableTextEditingController]. + /// + /// The state of this widget is persisted in a [RestorationBucket] claimed + /// from the surrounding [RestorationScope] using the provided restoration ID. + /// + /// See also: + /// + /// * [RestorationManager], which explains how state restoration works in + /// Flutter. + /// {@endtemplate} + final String? restorationId; + + /// {@macro flutter.widgets.editableText.scribbleEnabled} + final bool scribbleEnabled; + + /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning} + final bool enableIMEPersonalizedLearning; + + /// {@macro flutter.widgets.editableText.contentInsertionConfiguration} + final ContentInsertionConfiguration? contentInsertionConfiguration; + + /// {@macro flutter.widgets.EditableText.contextMenuBuilder} + /// + /// If not provided, will build a default menu based on the platform. + /// + /// See also: + /// + /// * [AdaptiveTextSelectionToolbar], which is built by default. + final EditableTextContextMenuBuilder? contextMenuBuilder; + + /// Determine whether this text field can request the primary focus. + /// + /// Defaults to true. If false, the text field will not request focus + /// when tapped, or when its context menu is displayed. If false it will not + /// be possible to move the focus to the text field with tab key. + final bool canRequestFocus; + + /// {@macro flutter.widgets.undoHistory.controller} + final UndoHistoryController? undoController; + + static Widget _defaultContextMenuBuilder( + BuildContext context, EditableTextState editableTextState) { + return AdaptiveTextSelectionToolbar.editableText( + editableTextState: editableTextState, + ); + } + + /// {@macro flutter.widgets.EditableText.spellCheckConfiguration} + /// + /// If [SpellCheckConfiguration.misspelledTextStyle] is not specified in this + /// configuration, then [materialMisspelledTextStyle] is used by default. + final SpellCheckConfiguration? spellCheckConfiguration; + + /// The [TextStyle] used to indicate misspelled words in the Material style. + /// + /// See also: + /// * [SpellCheckConfiguration.misspelledTextStyle], the style configured to + /// mark misspelled words with. + /// * [CupertinoTextField.cupertinoMisspelledTextStyle], the style configured + /// to mark misspelled words with in the Cupertino style. + static const TextStyle materialMisspelledTextStyle = TextStyle( + decoration: TextDecoration.underline, + decorationColor: Colors.red, + decorationStyle: TextDecorationStyle.wavy, + ); + + /// Default builder for [RichTextField]'s spell check suggestions toolbar. + /// + /// On Apple platforms, builds an iOS-style toolbar. Everywhere else, builds + /// an Android-style toolbar. + /// + /// See also: + /// * [spellCheckConfiguration], where this is typically specified for + /// [RichTextField]. + /// * [SpellCheckConfiguration.spellCheckSuggestionsToolbarBuilder], the + /// parameter for which this is the default value for [RichTextField]. + /// * [CupertinoTextField.defaultSpellCheckSuggestionsToolbarBuilder], which + /// is like this but specifies the default for [CupertinoTextField]. + @visibleForTesting + static Widget defaultSpellCheckSuggestionsToolbarBuilder( + BuildContext context, + EditableTextState editableTextState, + ) { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + return CupertinoSpellCheckSuggestionsToolbar.editableText( + editableTextState: editableTextState, + ); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return SpellCheckSuggestionsToolbar.editableText( + editableTextState: editableTextState, + ); + } + } + + /// Returns a new [SpellCheckConfiguration] where the given configuration has + /// had any missing values replaced with their defaults for the Android + /// platform. + static SpellCheckConfiguration inferAndroidSpellCheckConfiguration( + SpellCheckConfiguration? configuration, + ) { + if (configuration == null || + configuration == const SpellCheckConfiguration.disabled()) { + return const SpellCheckConfiguration.disabled(); + } + return configuration.copyWith( + misspelledTextStyle: configuration.misspelledTextStyle ?? + RichTextField.materialMisspelledTextStyle, + spellCheckSuggestionsToolbarBuilder: + configuration.spellCheckSuggestionsToolbarBuilder ?? + RichTextField.defaultSpellCheckSuggestionsToolbarBuilder, + ); + } + + @override + State createState() => _TextFieldState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty( + 'controller', controller, + defaultValue: null)); + properties.add(DiagnosticsProperty('focusNode', focusNode, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'undoController', undoController, + defaultValue: null)); + properties + .add(DiagnosticsProperty('enabled', enabled, defaultValue: null)); + properties.add(DiagnosticsProperty( + 'decoration', decoration, + defaultValue: const RichInputDecoration())); + properties.add(DiagnosticsProperty( + 'keyboardType', keyboardType, + defaultValue: TextInputType.text)); + properties.add( + DiagnosticsProperty('style', style, defaultValue: null)); + properties.add( + DiagnosticsProperty('autofocus', autofocus, defaultValue: false)); + properties.add(DiagnosticsProperty( + 'obscuringCharacter', obscuringCharacter, + defaultValue: '•')); + properties.add(DiagnosticsProperty('obscureText', obscureText, + defaultValue: false)); + properties.add(DiagnosticsProperty('autocorrect', autocorrect, + defaultValue: true)); + properties.add(EnumProperty( + 'smartDashesType', smartDashesType, + defaultValue: + obscureText ? SmartDashesType.disabled : SmartDashesType.enabled)); + properties.add(EnumProperty( + 'smartQuotesType', smartQuotesType, + defaultValue: + obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled)); + properties.add(DiagnosticsProperty( + 'enableSuggestions', enableSuggestions, + defaultValue: true)); + properties.add(IntProperty('maxLines', maxLines, defaultValue: 1)); + properties.add(IntProperty('minLines', minLines, defaultValue: null)); + properties.add( + DiagnosticsProperty('expands', expands, defaultValue: false)); + properties.add(IntProperty('maxLength', maxLength, defaultValue: null)); + properties.add(EnumProperty( + 'maxLengthEnforcement', maxLengthEnforcement, + defaultValue: null)); + properties.add(EnumProperty( + 'textInputAction', textInputAction, + defaultValue: null)); + properties.add(EnumProperty( + 'textCapitalization', textCapitalization, + defaultValue: TextCapitalization.none)); + properties.add(EnumProperty('textAlign', textAlign, + defaultValue: TextAlign.start)); + properties.add(DiagnosticsProperty( + 'textAlignVertical', textAlignVertical, + defaultValue: null)); + properties.add(EnumProperty('textDirection', textDirection, + defaultValue: null)); + properties + .add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0)); + properties + .add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null)); + properties.add(DiagnosticsProperty('cursorRadius', cursorRadius, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'cursorOpacityAnimates', cursorOpacityAnimates, + defaultValue: null)); + properties + .add(ColorProperty('cursorColor', cursorColor, defaultValue: null)); + properties.add(DiagnosticsProperty( + 'keyboardAppearance', keyboardAppearance, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'scrollPadding', scrollPadding, + defaultValue: const EdgeInsets.all(20.0))); + properties.add(FlagProperty('selectionEnabled', + value: selectionEnabled, + defaultValue: true, + ifFalse: 'selection disabled')); + properties.add(DiagnosticsProperty( + 'selectionControls', selectionControls, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'scrollController', scrollController, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'scrollPhysics', scrollPhysics, + defaultValue: null)); + properties.add(DiagnosticsProperty('clipBehavior', clipBehavior, + defaultValue: Clip.hardEdge)); + properties.add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, + defaultValue: true)); + properties.add(DiagnosticsProperty( + 'enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, + defaultValue: true)); + properties.add(DiagnosticsProperty( + 'spellCheckConfiguration', spellCheckConfiguration, + defaultValue: null)); + properties.add(DiagnosticsProperty>('contentCommitMimeTypes', + contentInsertionConfiguration?.allowedMimeTypes ?? const [], + defaultValue: contentInsertionConfiguration == null + ? const [] + : kDefaultContentInsertionMimeTypes)); + } +} + +class _TextFieldState extends State + with RestorationMixin + implements TextSelectionGestureDetectorBuilderDelegate, AutofillClient { + RestorableTextEditingController? _controller; + TextEditingController get _effectiveController => + widget.controller ?? _controller!.value; + + FocusNode? _focusNode; + FocusNode get _effectiveFocusNode => + widget.focusNode ?? (_focusNode ??= FocusNode()); + + MaxLengthEnforcement get _effectiveMaxLengthEnforcement => + widget.maxLengthEnforcement ?? + LengthLimitingTextInputFormatter.getDefaultMaxLengthEnforcement( + Theme.of(context).platform); + + bool _isHovering = false; + + bool get needsCounter => + widget.maxLength != null && + widget.decoration != null && + widget.decoration!.counterText == null; + + bool _showSelectionHandles = false; + + late _TextFieldSelectionGestureDetectorBuilder + _selectionGestureDetectorBuilder; + + // API for TextSelectionGestureDetectorBuilderDelegate. + @override + late bool forcePressEnabled; + + @override + final GlobalKey editableTextKey = + GlobalKey(); + + @override + bool get selectionEnabled => widget.selectionEnabled; + // End of API for TextSelectionGestureDetectorBuilderDelegate. + + bool get _isEnabled => widget.enabled ?? widget.decoration?.enabled ?? true; + + int get _currentLength => _effectiveController.value.text.characters.length; + + bool get _hasIntrinsicError => + widget.maxLength != null && + widget.maxLength! > 0 && + _effectiveController.value.text.characters.length > widget.maxLength!; + + bool get _hasError => + widget.decoration?.errorText != null || + widget.decoration?.error != null || + _hasIntrinsicError; + + Color get _errorColor => + widget.decoration?.errorStyle?.color ?? + Theme.of(context).colorScheme.error; + + RichInputDecoration _getEffectiveDecoration() { + final MaterialLocalizations localizations = + MaterialLocalizations.of(context); + final ThemeData themeData = Theme.of(context); + final RichInputDecoration effectiveDecoration = + (widget.decoration ?? const RichInputDecoration()) + .applyDefaults( + RTFConverter.convertMaterial2InputDecorationTheme( + themeData.inputDecorationTheme), + ) + .copyWith( + enabled: _isEnabled, + hintMaxLines: widget.decoration?.hintMaxLines ?? widget.maxLines, + ); + + // No need to build anything if counter or counterText were given directly. + if (effectiveDecoration.counter != null || + effectiveDecoration.counterText != null) { + return effectiveDecoration; + } + + // If buildCounter was provided, use it to generate a counter widget. + Widget? counter; + final int currentLength = _currentLength; + if (effectiveDecoration.counter == null && + effectiveDecoration.counterText == null && + widget.buildCounter != null) { + final bool isFocused = _effectiveFocusNode.hasFocus; + final Widget? builtCounter = widget.buildCounter!( + context, + currentLength: currentLength, + maxLength: widget.maxLength, + isFocused: isFocused, + ); + // If buildCounter returns null, don't add a counter widget to the field. + if (builtCounter != null) { + counter = Semantics( + container: true, + liveRegion: isFocused, + child: builtCounter, + ); + } + return effectiveDecoration.copyWith(counter: counter); + } + + if (widget.maxLength == null) { + return effectiveDecoration; + } // No counter widget + + String counterText = '$currentLength'; + String semanticCounterText = ''; + + // Handle a real maxLength (positive number) + if (widget.maxLength! > 0) { + // Show the maxLength in the counter + counterText += '/${widget.maxLength}'; + final int remaining = (widget.maxLength! - currentLength) + .clamp(0, widget.maxLength!); // ignore_clamp_double_lint + semanticCounterText = + localizations.remainingTextFieldCharacterCount(remaining); + } + + if (_hasIntrinsicError) { + return effectiveDecoration.copyWith( + errorText: effectiveDecoration.errorText ?? '', + counterStyle: effectiveDecoration.errorStyle ?? + (themeData.useMaterial3 + ? _m3CounterErrorStyle(context) + : _m2CounterErrorStyle(context)), + counterText: counterText, + semanticCounterText: semanticCounterText, + ); + } + + return effectiveDecoration.copyWith( + counterText: counterText, + semanticCounterText: semanticCounterText, + ); + } + + @override + void initState() { + super.initState(); + _selectionGestureDetectorBuilder = + _TextFieldSelectionGestureDetectorBuilder(state: this); + if (widget.controller == null) { + _createLocalController(); + } + _effectiveFocusNode.canRequestFocus = widget.canRequestFocus && _isEnabled; + _effectiveFocusNode.addListener(_handleFocusChanged); + } + + bool get _canRequestFocus { + final NavigationMode mode = + MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional; + switch (mode) { + case NavigationMode.traditional: + return widget.canRequestFocus && _isEnabled; + case NavigationMode.directional: + return true; + } + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _effectiveFocusNode.canRequestFocus = _canRequestFocus; + } + + @override + void didUpdateWidget(RichTextField oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller == null && oldWidget.controller != null) { + _createLocalController(oldWidget.controller!.value); + } else if (widget.controller != null && oldWidget.controller == null) { + unregisterFromRestoration(_controller!); + _controller!.dispose(); + _controller = null; + } + + if (widget.focusNode != oldWidget.focusNode) { + (oldWidget.focusNode ?? _focusNode)?.removeListener(_handleFocusChanged); + (widget.focusNode ?? _focusNode)?.addListener(_handleFocusChanged); + } + + _effectiveFocusNode.canRequestFocus = _canRequestFocus; + + if (_effectiveFocusNode.hasFocus && + widget.readOnly != oldWidget.readOnly && + _isEnabled) { + if (_effectiveController.selection.isCollapsed) { + _showSelectionHandles = !widget.readOnly; + } + } + } + + @override + void restoreState(RestorationBucket? oldBucket, bool initialRestore) { + if (_controller != null) { + _registerController(); + } + } + + void _registerController() { + assert(_controller != null); + registerForRestoration(_controller!, 'controller'); + } + + void _createLocalController([TextEditingValue? value]) { + assert(_controller == null); + _controller = value == null + ? RestorableTextEditingController() + : RestorableTextEditingController.fromValue(value); + if (!restorePending) { + _registerController(); + } + } + + @override + String? get restorationId => widget.restorationId; + + @override + void dispose() { + _effectiveFocusNode.removeListener(_handleFocusChanged); + _focusNode?.dispose(); + _controller?.dispose(); + super.dispose(); + } + + EditableTextState? get _editableText => editableTextKey.currentState; + + void _requestKeyboard() { + _editableText?.requestKeyboard(); + } + + bool _shouldShowSelectionHandles(SelectionChangedCause? cause) { + // When the text field is activated by something that doesn't trigger the + // selection overlay, we shouldn't show the handles either. + if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) { + return false; + } + + if (cause == SelectionChangedCause.keyboard) { + return false; + } + + if (widget.readOnly && _effectiveController.selection.isCollapsed) { + return false; + } + + if (!_isEnabled) { + return false; + } + + if (cause == SelectionChangedCause.longPress || + cause == SelectionChangedCause.scribble) { + return true; + } + + if (_effectiveController.text.isNotEmpty) { + return true; + } + + return false; + } + + void _handleFocusChanged() { + setState(() { + // Rebuild the widget on focus change to show/hide the text selection + // highlight. + }); + } + + void _handleSelectionChanged( + TextSelection selection, SelectionChangedCause? cause) { + final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause); + if (willShowSelectionHandles != _showSelectionHandles) { + setState(() { + _showSelectionHandles = willShowSelectionHandles; + }); + } + + switch (Theme.of(context).platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + case TargetPlatform.fuchsia: + case TargetPlatform.android: + if (cause == SelectionChangedCause.longPress) { + _editableText?.bringIntoView(selection.extent); + } + } + + switch (Theme.of(context).platform) { + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + case TargetPlatform.android: + break; + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + if (cause == SelectionChangedCause.drag) { + _editableText?.hideToolbar(); + } + } + } + + /// Toggle the toolbar when a selection handle is tapped. + void _handleSelectionHandleTapped() { + if (_effectiveController.selection.isCollapsed) { + _editableText!.toggleToolbar(); + } + } + + void _handleHover(bool hovering) { + if (hovering != _isHovering) { + setState(() { + _isHovering = hovering; + }); + } + } + + // AutofillClient implementation start. + @override + String get autofillId => _editableText!.autofillId; + + @override + void autofill(TextEditingValue newEditingValue) => + _editableText!.autofill(newEditingValue); + + @override + TextInputConfiguration get textInputConfiguration { + final List? autofillHints = + widget.autofillHints?.toList(growable: false); + final AutofillConfiguration autofillConfiguration = autofillHints != null + ? AutofillConfiguration( + uniqueIdentifier: autofillId, + autofillHints: autofillHints, + currentEditingValue: _effectiveController.value, + hintText: (widget.decoration ?? const RichInputDecoration()) + .hintTextSpan + ?.text, + ) + : AutofillConfiguration.disabled; + + return _editableText!.textInputConfiguration + .copyWith(autofillConfiguration: autofillConfiguration); + } + // AutofillClient implementation end. + + Set get _materialState { + return { + if (!_isEnabled) MaterialState.disabled, + if (_isHovering) MaterialState.hovered, + if (_effectiveFocusNode.hasFocus) MaterialState.focused, + if (_hasError) MaterialState.error, + }; + } + + TextStyle _getInputStyleForState(TextStyle style) { + final ThemeData theme = Theme.of(context); + final TextStyle stateStyle = MaterialStateProperty.resolveAs( + theme.useMaterial3 + ? _m3StateInputStyle(context)! + : _m2StateInputStyle(context)!, + _materialState); + final TextStyle providedStyle = + MaterialStateProperty.resolveAs(style, _materialState); + return providedStyle.merge(stateStyle); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMaterial(context)); + assert(debugCheckHasMaterialLocalizations(context)); + assert(debugCheckHasDirectionality(context)); + assert( + !(widget.style != null && + !widget.style!.inherit && + (widget.style!.fontSize == null || + widget.style!.textBaseline == null)), + 'inherit false style must supply fontSize and textBaseline', + ); + + final ThemeData theme = Theme.of(context); + final DefaultSelectionStyle selectionStyle = + DefaultSelectionStyle.of(context); + final TextStyle? providedStyle = + MaterialStateProperty.resolveAs(widget.style, _materialState); + final TextStyle style = _getInputStyleForState(theme.useMaterial3 + ? _m3InputStyle(context) + : theme.textTheme.titleMedium!) + .merge(providedStyle); + final Brightness keyboardAppearance = + widget.keyboardAppearance ?? theme.brightness; + final TextEditingController controller = _effectiveController; + final FocusNode focusNode = _effectiveFocusNode; + final List formatters = [ + ...?widget.inputFormatters, + if (widget.maxLength != null) + LengthLimitingTextInputFormatter( + widget.maxLength, + maxLengthEnforcement: _effectiveMaxLengthEnforcement, + ), + ]; + + // Set configuration as disabled if not otherwise specified. If specified, + // ensure that configuration uses the correct style for misspelled words for + // the current platform, unless a custom style is specified. + final SpellCheckConfiguration spellCheckConfiguration; + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + spellCheckConfiguration = + CupertinoTextField.inferIOSSpellCheckConfiguration( + widget.spellCheckConfiguration, + ); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + spellCheckConfiguration = + RichTextField.inferAndroidSpellCheckConfiguration( + widget.spellCheckConfiguration, + ); + } + + TextSelectionControls? textSelectionControls = widget.selectionControls; + final bool paintCursorAboveText; + bool? cursorOpacityAnimates = widget.cursorOpacityAnimates; + Offset? cursorOffset; + final Color cursorColor; + final Color selectionColor; + Color? autocorrectionTextRectColor; + Radius? cursorRadius = widget.cursorRadius; + VoidCallback? handleDidGainAccessibilityFocus; + VoidCallback? handleDidLoseAccessibilityFocus; + + switch (theme.platform) { + case TargetPlatform.iOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = true; + textSelectionControls ??= cupertinoTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates ??= true; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + autocorrectionTextRectColor = selectionColor; + + case TargetPlatform.macOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = false; + textSelectionControls ??= cupertinoDesktopTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + handleDidGainAccessibilityFocus = () { + // Automatically activate the RichTextField when it receives accessibility focus. + if (!_effectiveFocusNode.hasFocus && + _effectiveFocusNode.canRequestFocus) { + _effectiveFocusNode.requestFocus(); + } + }; + handleDidLoseAccessibilityFocus = () { + _effectiveFocusNode.unfocus(); + }; + + case TargetPlatform.android: + case TargetPlatform.fuchsia: + forcePressEnabled = false; + textSelectionControls ??= materialTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + + case TargetPlatform.linux: + forcePressEnabled = false; + textSelectionControls ??= desktopTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + handleDidGainAccessibilityFocus = () { + // Automatically activate the RichTextField when it receives accessibility focus. + if (!_effectiveFocusNode.hasFocus && + _effectiveFocusNode.canRequestFocus) { + _effectiveFocusNode.requestFocus(); + } + }; + handleDidLoseAccessibilityFocus = () { + _effectiveFocusNode.unfocus(); + }; + + case TargetPlatform.windows: + forcePressEnabled = false; + textSelectionControls ??= desktopTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + handleDidGainAccessibilityFocus = () { + // Automatically activate the RichTextField when it receives accessibility focus. + if (!_effectiveFocusNode.hasFocus && + _effectiveFocusNode.canRequestFocus) { + _effectiveFocusNode.requestFocus(); + } + }; + handleDidLoseAccessibilityFocus = () { + _effectiveFocusNode.unfocus(); + }; + } + + Widget child = RepaintBoundary( + child: UnmanagedRestorationScope( + bucket: bucket, + child: EditableText( + key: editableTextKey, + readOnly: widget.readOnly || !_isEnabled, + showCursor: widget.showCursor, + showSelectionHandles: _showSelectionHandles, + controller: controller, + focusNode: focusNode, + undoController: widget.undoController, + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + textCapitalization: widget.textCapitalization, + style: style, + strutStyle: widget.strutStyle, + textAlign: widget.textAlign, + textDirection: widget.textDirection, + autofocus: widget.autofocus, + obscuringCharacter: widget.obscuringCharacter, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + enableSuggestions: widget.enableSuggestions, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + // Only show the selection highlight when the text field is focused. + selectionColor: focusNode.hasFocus ? selectionColor : null, + selectionControls: + widget.selectionEnabled ? textSelectionControls : null, + onChanged: widget.onChanged, + onSelectionChanged: _handleSelectionChanged, + onEditingComplete: widget.onEditingComplete, + onSubmitted: widget.onSubmitted, + onAppPrivateCommand: widget.onAppPrivateCommand, + onSelectionHandleTapped: _handleSelectionHandleTapped, + onTapOutside: widget.onTapOutside, + inputFormatters: formatters, + rendererIgnoresPointer: true, + mouseCursor: + MouseCursor.defer, // RichTextField will handle the cursor + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: cursorRadius, + cursorColor: cursorColor, + selectionHeightStyle: widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + cursorOpacityAnimates: cursorOpacityAnimates, + cursorOffset: cursorOffset, + paintCursorAboveText: paintCursorAboveText, + backgroundCursorColor: CupertinoColors.inactiveGray, + scrollPadding: widget.scrollPadding, + keyboardAppearance: keyboardAppearance, + enableInteractiveSelection: widget.enableInteractiveSelection, + dragStartBehavior: widget.dragStartBehavior, + scrollController: widget.scrollController, + scrollPhysics: widget.scrollPhysics, + autofillClient: this, + autocorrectionTextRectColor: autocorrectionTextRectColor, + clipBehavior: widget.clipBehavior, + restorationId: 'editable', + scribbleEnabled: widget.scribbleEnabled, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + contentInsertionConfiguration: widget.contentInsertionConfiguration, + contextMenuBuilder: widget.contextMenuBuilder, + spellCheckConfiguration: spellCheckConfiguration, + magnifierConfiguration: widget.magnifierConfiguration ?? + TextMagnifier.adaptiveMagnifierConfiguration, + ), + ), + ); + + if (widget.decoration != null) { + child = AnimatedBuilder( + animation: Listenable.merge([focusNode, controller]), + builder: (BuildContext context, Widget? child) { + return RichInputDecorator( + decoration: _getEffectiveDecoration(), + baseStyle: widget.style, + textAlign: widget.textAlign, + textAlignVertical: widget.textAlignVertical, + isHovering: _isHovering, + isFocused: focusNode.hasFocus, + isEmpty: controller.value.text.isEmpty, + expands: widget.expands, + child: child, + ); + }, + child: child, + ); + } + final MouseCursor effectiveMouseCursor = + MaterialStateProperty.resolveAs( + widget.mouseCursor ?? MaterialStateMouseCursor.textable, + _materialState, + ); + + final int? semanticsMaxValueLength; + if (_effectiveMaxLengthEnforcement != MaxLengthEnforcement.none && + widget.maxLength != null && + widget.maxLength! > 0) { + semanticsMaxValueLength = widget.maxLength; + } else { + semanticsMaxValueLength = null; + } + + return MouseRegion( + cursor: effectiveMouseCursor, + onEnter: (PointerEnterEvent event) => _handleHover(true), + onExit: (PointerExitEvent event) => _handleHover(false), + child: TextFieldTapRegion( + child: IgnorePointer( + ignoring: !_isEnabled, + child: AnimatedBuilder( + animation: controller, // changes the _currentLength + builder: (BuildContext context, Widget? child) { + return Semantics( + maxValueLength: semanticsMaxValueLength, + currentValueLength: _currentLength, + onTap: widget.readOnly + ? null + : () { + if (!_effectiveController.selection.isValid) { + _effectiveController.selection = + TextSelection.collapsed( + offset: _effectiveController.text.length); + } + _requestKeyboard(); + }, + onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus, + onDidLoseAccessibilityFocus: handleDidLoseAccessibilityFocus, + child: child, + ); + }, + child: _selectionGestureDetectorBuilder.buildGestureDetector( + behavior: HitTestBehavior.translucent, + child: child, + ), + ), + ), + ), + ); + } +} + +TextStyle? _m2StateInputStyle(BuildContext context) => + MaterialStateTextStyle.resolveWith((Set states) { + final ThemeData theme = Theme.of(context); + if (states.contains(MaterialState.disabled)) { + return TextStyle(color: theme.disabledColor); + } + return TextStyle(color: theme.textTheme.titleMedium?.color); + }); + +TextStyle _m2CounterErrorStyle(BuildContext context) => Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Theme.of(context).colorScheme.error); + +// BEGIN GENERATED TOKEN PROPERTIES - RichTextField + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +TextStyle? _m3StateInputStyle(BuildContext context) => + MaterialStateTextStyle.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return TextStyle( + color: Theme.of(context) + .textTheme + .bodyLarge! + .color + ?.withOpacity(0.38)); + } + return TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color); + }); + +TextStyle _m3InputStyle(BuildContext context) => + Theme.of(context).textTheme.bodyLarge!; + +TextStyle _m3CounterErrorStyle(BuildContext context) => Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Theme.of(context).colorScheme.error); + +// END GENERATED TOKEN PROPERTIES - RichTextField diff --git a/lib/src/view/controller/base_rtf_controller.dart b/lib/src/view/controller/base_rtf_controller.dart new file mode 100644 index 0000000..a57322f --- /dev/null +++ b/lib/src/view/controller/base_rtf_controller.dart @@ -0,0 +1,446 @@ +part of 'rtf_text_field_controller.dart'; + +class _RichTextEditorController extends TextEditingController { + ///This holds all the text changes per character and it's corresponding style/metadata + final TextDeltas deltas; + + /// This holds the state of the text styles. + /// on every selection change (collapsed or not) it's value defaults to that + /// of the [TextDelta] before it in the [deltas] list or the [defaultMetadata] if it's the first + RTFTextMetadata? _metadata; + + RTFTextMetadata? get metadata => _metadata; + + /// This is holds the state of the metadata change temporarily. + /// + /// it is reset when it's getter is called + bool _metadataToggled = false; + + bool get metadataToggled { + if (_metadataToggled) { + final bool value = _metadataToggled; + _metadataToggled = false; + return value; + } + return _metadataToggled; + } + + set metadataToggled(bool value) { + _metadataToggled = value; + } + + set metadata(RTFTextMetadata? value) { + _metadata = value; + notifyListeners(); + } + + /// returns a copy of this controller + /// + /// why? because all flutter [Listenable] objects are stored in memory and passed by reference + _RichTextEditorController copy() { + return _RichTextEditorController( + text: text, + deltas: deltas.copy, + ) + ..value = value + ..metadata = metadata; + } + + /// returns a copy of this controller with the given parameters + _RichTextEditorController copyWith({ + TextDeltas? deltas, + TextEditingValue? value, + RTFTextMetadata? metadata, + }) { + return _RichTextEditorController( + text: text, + deltas: deltas?.copy ?? this.deltas.copy, + ) + ..value = value ?? this.value + ..metadata = metadata ?? this.metadata; + } + + _RichTextEditorController({ + super.text, + TextDeltas? deltas, + RTFTextMetadata? metaData, + }) : _metadata = metaData ?? RTFTextFieldController.defaultMetadata, + deltas = deltas ?? + (text == null ? [] : RTFTextDeltasUtils.deltasFromString(text)) { + addListener(_internalControllerListener); + } + + void _internalControllerListener() { + TextDeltas newDeltas = compareNewStringAndOldTextDeltasForChanges( + text, + deltas.copy, + ); + + if (isListMode && newDeltas.length != deltas.length) { + newDeltas = modifyDeltasForBulletListChange( + newDeltas, + deltas.copy, + ); + } + + setDeltas(newDeltas); + } + + void setDeltas(TextDeltas newDeltas) { + deltas.clear(); + deltas.addAll(newDeltas); + if (selection.isCollapsed) resetMetadataOnSelectionCollapsed(); + } + + void resetMetadataOnSelectionCollapsed() { + if (!selection.isCollapsed) return; + if (selection.end == text.length || textBeforeSelection().isNullOrEmpty) { + return; + } + if (_metadataToggled) return; + + final RTFTextMetadata newMetadata = (deltas.isNotEmpty + ? deltas[text.indexOf(selection.textBefore(text).chars.last)] + .metadata + : metadata) ?? + metadata ?? + RTFTextFieldController.defaultMetadata; + + _metadata = _metadata?.combineWith( + newMetadata, + favourOther: true, + ) ?? + newMetadata; + } + + String? textBeforeSelection() { + try { + return selection.textBefore(text); + } catch (e) { + return null; + } + } + + static const String bulletPoint = '•'; + + List modifyDeltasForBulletListChange( + List modifiedDeltas, + List oldDeltas, + ) { + final List oldChars = oldDeltas.text.characters.toList(); + final List newChars = modifiedDeltas.text.characters.toList(); + + if (oldChars.length > newChars.length) return modifiedDeltas; + + if (newChars.last == '\n') { + const String value = '\n $bulletPoint '; + + final TextDeltas deltas = modifiedDeltas.copy + ..replaceRange( + modifiedDeltas.length - 1, + modifiedDeltas.length, + List.generate( + value.length, + (index) => RTFTextDelta( + char: value[index], + + /// adding this check so that the character typed after this does not inherit the bullet point's metadata + /// hence the restoration back to the [this] controller's [metadata] + metadata: (index == value.length - 1) + ? metadata + : RTFTextFieldController.defaultMetadata.copyWith( + fontWeight: FontWeight.bold, + ), + ), + ), + ); + + text = deltas.text; + selection = TextSelection.collapsed(offset: text.length); + + return deltas; + } + + return modifiedDeltas; + } + + TextDeltas compareNewStringAndOldTextDeltasForChanges( + String text, + TextDeltas oldDeltas, + ) { + if (text.isEmpty) return []; + final int minLength = min(text.length, oldDeltas.length); + + final TextDeltas deltas = + oldDeltas.isEmpty ? [] : oldDeltas.sublist(0, minLength); + + if (minLength == oldDeltas.length) { + final RTFTextMetadata metadata_ = + oldDeltas.elementAtOrNull(minLength - 1)?.metadata ?? + metadata ?? + RTFTextFieldController.defaultMetadata; + + for (int i = minLength; i < text.length; i++) { + deltas.add( + RTFTextDelta( + char: text[i], + metadata: metadataToggled ? metadata : metadata_, + ), + ); + } + } else { + for (int i = minLength; i < oldDeltas.length; i++) { + deltas.removeLast(); + } + } + + return deltas; + } + + void applyDefaultMetadataChange(RTFTextMetadata changedMetadata) { + metadata = changedMetadata; + } + + bool get isListMode => indexOflListChar != null; + + int? indexOflListChar; + + void toggleListMode() { + indexOflListChar = indexOflListChar == null ? (deltas.length - 1) : null; + _metadataToggled = true; + notifyListeners(); + } + + void changeStyleOnSelectionChange({ + RTFTextMetadata? changedMetadata, + required RTFTextMetadataChangeEnum change, + required TextDeltas modifiedDeltas, + required TextSelection selection, + }) { + if (!selection.isValid) return; + changedMetadata ??= + deltas[text.indexOf(selection.textBefore(text).chars.last)].metadata ?? + metadata ?? + RTFTextFieldController.defaultMetadata; + + _metadata = _metadata?.combineWhatChanged( + change, + changedMetadata, + ) ?? + changedMetadata; + + metadataToggled = true; + + if (selection.isCollapsed) return notifyListeners(); + + setDeltas( + applyMetadataToTextInSelection( + newMetadata: changedMetadata, + change: change, + deltas: modifiedDeltas, + selection: selection, + ), + ); + notifyListeners(); + } + + /// Applies the [newMetadata] to the [deltas] in the [selection] by the [change]. + /// + /// use [RTFTextMetadataChangeEnum.all] to apply change to more than one metadata field change. + TextDeltas applyMetadataToTextInSelection({ + required RTFTextMetadata newMetadata, + required TextDeltas deltas, + required RTFTextMetadataChangeEnum change, + required TextSelection selection, + }) { + final TextDeltas modifiedDeltas = deltas.copy; + + final int start = selection.start; + final int end = selection.end; + + for (int i = start; i < end; i++) { + modifiedDeltas[i] = modifiedDeltas[i].copyWith( + metadata: modifiedDeltas[i].metadata?.combineWhatChanged( + change, + newMetadata, + ) ?? + newMetadata, + ); + } + return modifiedDeltas; + } + + /// Toggles the [RTFTextMetadata.fontWeight] between [FontWeight.normal] and [FontWeight.w700]. + void toggleBold() { + final RTFTextMetadata tempMetadata = + metadata ?? RTFTextFieldController.defaultMetadata; + final RTFTextMetadata changedMetadata = tempMetadata.copyWith( + fontWeight: tempMetadata.fontWeight == FontWeight.normal + ? FontWeight.w700 + : FontWeight.normal, + ); + + changeStyleOnSelectionChange( + changedMetadata: changedMetadata, + change: RTFTextMetadataChangeEnum.fontWeight, + modifiedDeltas: deltas.copy, + selection: selection.copyWith(), + ); + } + + /// Toggles the [RTFTextMetadata.fontStyle] between [FontStyle.normal] and [FontStyle.italic]. + void toggleItalic() { + final RTFTextMetadata tempMetadata = + metadata ?? RTFTextFieldController.defaultMetadata; + + final RTFTextMetadata changedMetadata = tempMetadata.copyWith( + fontStyle: tempMetadata.fontStyle == FontStyle.italic + ? FontStyle.normal + : FontStyle.italic, + ); + changeStyleOnSelectionChange( + changedMetadata: changedMetadata, + change: RTFTextMetadataChangeEnum.fontStyle, + modifiedDeltas: deltas.copy, + selection: selection, + ); + } + + /// Toggles the [RTFTextMetadata.decoration] between [RTFTextDecorationEnum.none] and [RTFTextDecorationEnum.underline]. + void toggleUnderline() { + final RTFTextMetadata tempMetadata = + metadata ?? RTFTextFieldController.defaultMetadata; + + final RTFTextMetadata changedMetadata = tempMetadata.copyWith( + decoration: tempMetadata.decoration == RTFTextDecorationEnum.underline + ? RTFTextDecorationEnum.none + : RTFTextDecorationEnum.underline, + ); + + changeStyleOnSelectionChange( + changedMetadata: changedMetadata, + change: RTFTextMetadataChangeEnum.fontDecoration, + modifiedDeltas: deltas.copy, + selection: selection, + ); + } + + /// Toggles the [RTFTextMetadata.decoration] between [RTFTextDecorationEnum.none] and [RTFTextDecorationEnum.lineThrough]. + void toggleSuperscript() { + final RTFTextMetadata tempMetadata = + metadata ?? RTFTextFieldController.defaultMetadata; + + final RTFTextMetadata changedMetadata = tempMetadata.copyWith( + fontFeatures: tempMetadata.fontFeatures?.firstOrNull == + const FontFeature.superscripts() + ? const [] + : const [FontFeature.superscripts()], + ); + + changeStyleOnSelectionChange( + changedMetadata: changedMetadata, + change: RTFTextMetadataChangeEnum.fontFeatures, + modifiedDeltas: deltas.copy, + selection: selection, + ); + } + + /// Toggles the [RTFTextMetadata.fontFeatures] between empty list and [FontFeature.subscripts()]. + void toggleSubscript() { + final RTFTextMetadata tempMetadata = + metadata ?? RTFTextFieldController.defaultMetadata; + + final RTFTextMetadata changedMetadata = tempMetadata.copyWith( + fontFeatures: tempMetadata.fontFeatures?.firstOrNull == + const FontFeature.subscripts() + ? const [] + : const [FontFeature.subscripts()], + ); + + changeStyleOnSelectionChange( + changedMetadata: changedMetadata, + change: RTFTextMetadataChangeEnum.fontFeatures, + modifiedDeltas: deltas.copy, + selection: selection, + ); + } + + void changeColor(Color color) { + final RTFTextMetadata changedMetadata = + (metadata ?? RTFTextFieldController.defaultMetadata).copyWith( + color: color, + ); + changeStyleOnSelectionChange( + changedMetadata: changedMetadata, + change: RTFTextMetadataChangeEnum.fontStyle, + modifiedDeltas: deltas.copy, + selection: selection, + ); + } + + void changeFontSize(double fontSize) { + final RTFTextMetadata changedMetadata = + (metadata ?? RTFTextFieldController.defaultMetadata).copyWith( + fontSize: fontSize, + ); + changeStyleOnSelectionChange( + changedMetadata: changedMetadata, + change: RTFTextMetadataChangeEnum.fontSize, + modifiedDeltas: deltas.copy, + selection: selection, + ); + } + + /// Changes the [RTFTextMetadata.alignment] to the given [alignment]. + /// + /// note that you have to use [RichTextField] for changes made by this method to reflect. + /// or otherwise set the [TextField.alignment] parameter of your textfield to [RTFTextMetadata.alignment] + /// while listening to changes in the controller. + /// example: + ///... + /// ValueListenableBuilder( + /// valueListenable: controller, + /// builder: (_, controllerValue, __) => TextField( + /// controller: controller, + /// textAlign: controller.metadata?.alignment ?? TextAlign.start, + /// ), + /// ), + /// ... + void changeAlignment(TextAlign alignment) { + applyDefaultMetadataChange( + (metadata ?? RTFTextFieldController.defaultMetadata) + .copyWith(alignment: alignment), + ); + } + + @override + TextSpan buildTextSpan({ + required BuildContext context, + TextStyle? style, + required bool withComposing, + }) { + final List spanChildren = []; + + for (final RTFTextDelta delta in deltas) { + spanChildren.add( + TextSpan( + text: delta.char, + style: delta.metadata?.style ?? + RTFTextFieldController.defaultMetadata.style, + ), + ); + } + + final TextSpan textSpan = TextSpan( + style: metadata?.styleWithoutFontFeatures ?? style, + children: spanChildren, + ); + return textSpan; + } + + @override + void dispose() { + removeListener(_internalControllerListener); + super.dispose(); + } +} diff --git a/lib/src/view/controller/rtf_text_field_controller.dart b/lib/src/view/controller/rtf_text_field_controller.dart new file mode 100644 index 0000000..1f51989 --- /dev/null +++ b/lib/src/view/controller/rtf_text_field_controller.dart @@ -0,0 +1,94 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:rtf_textfield/src/core/models/text_delta.dart'; +import 'package:rtf_textfield/src/core/models/text_metadata.dart'; +import 'package:rtf_textfield/src/core/utils/enums/text_decoration.dart'; +import 'package:rtf_textfield/src/core/utils/enums/text_metadata.dart'; +import 'package:rtf_textfield/src/core/utils/extensions/list.dart'; +import 'package:rtf_textfield/src/core/utils/extensions/string.dart'; +import 'package:rtf_textfield/src/core/utils/extensions/text_deltas.dart'; +import 'package:rtf_textfield/src/core/utils/text_deltas.dart'; + +part 'base_rtf_controller.dart'; + +/// This is the main controller for the text editor +class RTFTextFieldController extends _RichTextEditorController { + ///This holds all the text changes per character and it's corresponding style/metadata + @override + // ignore: overridden_fields + final TextDeltas deltas; + + static const RTFTextMetadata defaultMetadata = RTFTextMetadata( + alignment: TextAlign.start, + decoration: RTFTextDecorationEnum.none, + fontSize: 14, + fontStyle: FontStyle.normal, + fontWeight: FontWeight.w400, + fontFeatures: null, + ); + + /// Constructs an instance of [RTFTextFieldController] with the provided [text] and [deltas] + /// + /// if [text] is not provided, it will be generated from the [deltas]. + /// if [delta] is not provided, it will be generated from the [text] and [metadata]. + /// [metadata] is optional and if not provided, it will be set to [defaultMetadata] + RTFTextFieldController({ + String? text, + TextDeltas? deltas, + RTFTextMetadata? metadata, + }) : deltas = deltas ?? + (text == null + ? [] + : RTFTextDeltasUtils.deltasFromString( + text, + metadata ?? defaultMetadata, + )), + super( + text: text ?? deltas?.text, + metaData: metadata, + ) { + addListener(_internalControllerListener); + } + + @override + RTFTextFieldController copy() { + return RTFTextFieldController( + text: text, + deltas: deltas.copy, + ) + ..value = value + ..metadata = metadata; + } + + /// Data serializer method for this class + Map toMap() { + return { + 'text': text, + 'deltas': deltas.map((RTFTextDelta delta) => delta.toMap()).toList(), + 'metadata': metadata?.toMap(), + 'value': value.toJSON(), + }; + } + + /// Data deserializer method for this class + /// + /// This is used to create a new instance of this class from a map + factory RTFTextFieldController.fromMap(Map map) { + return RTFTextFieldController( + text: map['text'] as String, + deltas: RTFTextDeltasUtils.deltasFromList( + (map['deltas'] as List).cast(), + ), + ) + ..value = TextEditingValue.fromJSON( + (map['value'] as Map).cast(), + ) + ..metadata = map['metadata'] == null + ? null + : RTFTextMetadata.fromMap( + (map['metadata'] as Map).cast(), + ); + } +} diff --git a/lib/src/view/input_decoration/rtf_input_decorator.dart b/lib/src/view/input_decoration/rtf_input_decorator.dart new file mode 100644 index 0000000..0b88527 --- /dev/null +++ b/lib/src/view/input_decoration/rtf_input_decorator.dart @@ -0,0 +1,5041 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math' as math; +import 'dart:ui' show lerpDouble; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:rtf_textfield/src/core/utils/converter.dart'; + +// Examples can assume: +// late Widget _myIcon; + +// The duration value extracted from: +// https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/textfield/TextInputLayout.java +const Duration _kTransitionDuration = Duration(milliseconds: 167); +const Curve _kTransitionCurve = Curves.fastOutSlowIn; +const double _kFinalLabelScale = 0.75; + +// The default duration for hint fade in/out transitions. +// +// Animating hint is not mentioned in the Material specification. +// The animation is kept for backard compatibility and a short duration +// is used to mitigate the UX impact. +const Duration _kHintFadeTransitionDuration = Duration(milliseconds: 20); + +// Defines the gap in the RichInputDecorator's outline border where the +// floating label will appear. +class _InputBorderGap extends ChangeNotifier { + double? _start; + double? get start => _start; + set start(double? value) { + if (value != _start) { + _start = value; + notifyListeners(); + } + } + + double _extent = 0.0; + double get extent => _extent; + set extent(double value) { + if (value != _extent) { + _extent = value; + notifyListeners(); + } + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes, this class is not used in collection + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is _InputBorderGap && + other.start == start && + other.extent == extent; + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes, this class is not used in collection + int get hashCode => Object.hash(start, extent); + + @override + String toString() => describeIdentity(this); +} + +// Used to interpolate between two InputBorders. +class _InputBorderTween extends Tween { + _InputBorderTween({super.begin, super.end}); + + @override + InputBorder lerp(double t) => ShapeBorder.lerp(begin, end, t)! as InputBorder; +} + +// Passes the _InputBorderGap parameters along to an InputBorder's paint method. +class _InputBorderPainter extends CustomPainter { + _InputBorderPainter({ + required Listenable repaint, + required this.borderAnimation, + required this.border, + required this.gapAnimation, + required this.gap, + required this.textDirection, + required this.fillColor, + required this.hoverAnimation, + required this.hoverColorTween, + }) : super(repaint: repaint); + + final Animation borderAnimation; + final _InputBorderTween border; + final Animation gapAnimation; + final _InputBorderGap gap; + final TextDirection textDirection; + final Color fillColor; + final ColorTween hoverColorTween; + final Animation hoverAnimation; + + Color get blendedColor => + Color.alphaBlend(hoverColorTween.evaluate(hoverAnimation)!, fillColor); + + @override + void paint(Canvas canvas, Size size) { + final InputBorder borderValue = border.evaluate(borderAnimation); + final Rect canvasRect = Offset.zero & size; + final Color blendedFillColor = blendedColor; + if (blendedFillColor.alpha > 0) { + canvas.drawPath( + borderValue.getOuterPath(canvasRect, textDirection: textDirection), + Paint() + ..color = blendedFillColor + ..style = PaintingStyle.fill, + ); + } + + borderValue.paint( + canvas, + canvasRect, + gapStart: gap.start, + gapExtent: gap.extent, + gapPercentage: gapAnimation.value, + textDirection: textDirection, + ); + } + + @override + bool shouldRepaint(_InputBorderPainter oldPainter) { + return borderAnimation != oldPainter.borderAnimation || + hoverAnimation != oldPainter.hoverAnimation || + gapAnimation != oldPainter.gapAnimation || + border != oldPainter.border || + gap != oldPainter.gap || + textDirection != oldPainter.textDirection; + } + + @override + String toString() => describeIdentity(this); +} + +// An analog of AnimatedContainer, which can animate its shaped border, for +// _InputBorder. This specialized animated container is needed because the +// _InputBorderGap, which is computed at layout time, is required by the +// _InputBorder's paint method. +class _BorderContainer extends StatefulWidget { + const _BorderContainer({ + required this.border, + required this.gap, + required this.gapAnimation, + required this.fillColor, + required this.hoverColor, + required this.isHovering, + }); + + final InputBorder border; + final _InputBorderGap gap; + final Animation gapAnimation; + final Color fillColor; + final Color hoverColor; + final bool isHovering; + + @override + _BorderContainerState createState() => _BorderContainerState(); +} + +class _BorderContainerState extends State<_BorderContainer> + with TickerProviderStateMixin { + static const Duration _kHoverDuration = Duration(milliseconds: 15); + + late AnimationController _controller; + late AnimationController _hoverColorController; + late Animation _borderAnimation; + late _InputBorderTween _border; + late Animation _hoverAnimation; + late ColorTween _hoverColorTween; + + @override + void initState() { + super.initState(); + _hoverColorController = AnimationController( + duration: _kHoverDuration, + value: widget.isHovering ? 1.0 : 0.0, + vsync: this, + ); + _controller = AnimationController( + duration: _kTransitionDuration, + vsync: this, + ); + _borderAnimation = CurvedAnimation( + parent: _controller, + curve: _kTransitionCurve, + reverseCurve: _kTransitionCurve.flipped, + ); + _border = _InputBorderTween( + begin: widget.border, + end: widget.border, + ); + _hoverAnimation = CurvedAnimation( + parent: _hoverColorController, + curve: Curves.linear, + ); + _hoverColorTween = + ColorTween(begin: Colors.transparent, end: widget.hoverColor); + } + + @override + void dispose() { + _controller.dispose(); + _hoverColorController.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(_BorderContainer oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.border != oldWidget.border) { + _border = _InputBorderTween( + begin: oldWidget.border, + end: widget.border, + ); + _controller + ..value = 0.0 + ..forward(); + } + if (widget.hoverColor != oldWidget.hoverColor) { + _hoverColorTween = + ColorTween(begin: Colors.transparent, end: widget.hoverColor); + } + if (widget.isHovering != oldWidget.isHovering) { + if (widget.isHovering) { + _hoverColorController.forward(); + } else { + _hoverColorController.reverse(); + } + } + } + + @override + Widget build(BuildContext context) { + return CustomPaint( + foregroundPainter: _InputBorderPainter( + repaint: Listenable.merge([ + _borderAnimation, + widget.gap, + _hoverColorController, + ]), + borderAnimation: _borderAnimation, + border: _border, + gapAnimation: widget.gapAnimation, + gap: widget.gap, + textDirection: Directionality.of(context), + fillColor: widget.fillColor, + hoverColorTween: _hoverColorTween, + hoverAnimation: _hoverAnimation, + ), + ); + } +} + +// Used to "shake" the floating label to the left and right +// when the errorText first appears. +class _Shaker extends AnimatedWidget { + const _Shaker({ + required Animation animation, + this.child, + }) : super(listenable: animation); + + final Widget? child; + + Animation get animation => listenable as Animation; + + double get translateX { + const double shakeDelta = 4.0; + final double t = animation.value; + if (t <= 0.25) { + return -t * shakeDelta; + } else if (t < 0.75) { + return (t - 0.5) * shakeDelta; + } else { + return (1.0 - t) * 4.0 * shakeDelta; + } + } + + @override + Widget build(BuildContext context) { + return Transform( + transform: Matrix4.translationValues(translateX, 0.0, 0.0), + child: child, + ); + } +} + +// Display the helper and error text. When the error text appears +// it fades and the helper text fades out. The error text also +// slides upwards a little when it first appears. +class _HelperError extends StatefulWidget { + const _HelperError({ + this.textAlign, + this.helperText, + this.helperStyle, + this.helperMaxLines, + this.error, + this.errorText, + this.errorStyle, + this.errorMaxLines, + }); + + final TextAlign? textAlign; + final String? helperText; + final TextStyle? helperStyle; + final int? helperMaxLines; + final Widget? error; + final String? errorText; + final TextStyle? errorStyle; + final int? errorMaxLines; + + @override + _HelperErrorState createState() => _HelperErrorState(); +} + +class _HelperErrorState extends State<_HelperError> + with SingleTickerProviderStateMixin { + // If the height of this widget and the counter are zero ("empty") at + // layout time, no space is allocated for the subtext. + static const Widget empty = SizedBox.shrink(); + + late AnimationController _controller; + Widget? _helper; + Widget? _error; + + bool get _hasError => widget.errorText != null || widget.error != null; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: _kTransitionDuration, + vsync: this, + ); + if (_hasError) { + _error = _buildError(); + _controller.value = 1.0; + } else if (widget.helperText != null) { + _helper = _buildHelper(); + } + _controller.addListener(_handleChange); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _handleChange() { + setState(() { + // The _controller's value has changed. + }); + } + + @override + void didUpdateWidget(_HelperError old) { + super.didUpdateWidget(old); + + final Widget? newError = widget.error; + final String? newErrorText = widget.errorText; + final String? newHelperText = widget.helperText; + final Widget? oldError = old.error; + final String? oldErrorText = old.errorText; + final String? oldHelperText = old.helperText; + + final bool errorStateChanged = (newError != null) != (oldError != null); + final bool errorTextStateChanged = + (newErrorText != null) != (oldErrorText != null); + final bool helperTextStateChanged = newErrorText == null && + (newHelperText != null) != (oldHelperText != null); + + if (errorStateChanged || errorTextStateChanged || helperTextStateChanged) { + if (newError != null || newErrorText != null) { + _error = _buildError(); + _controller.forward(); + } else if (newHelperText != null) { + _helper = _buildHelper(); + _controller.reverse(); + } else { + _controller.reverse(); + } + } + } + + Widget _buildHelper() { + assert(widget.helperText != null); + return Semantics( + container: true, + child: FadeTransition( + opacity: Tween(begin: 1.0, end: 0.0).animate(_controller), + child: Text( + widget.helperText!, + style: widget.helperStyle, + textAlign: widget.textAlign, + overflow: TextOverflow.ellipsis, + maxLines: widget.helperMaxLines, + ), + ), + ); + } + + Widget _buildError() { + assert(widget.error != null || widget.errorText != null); + return Semantics( + container: true, + child: FadeTransition( + opacity: _controller, + child: FractionalTranslation( + translation: Tween( + begin: const Offset(0.0, -0.25), + end: Offset.zero, + ).evaluate(_controller.view), + child: widget.error ?? + Text( + widget.errorText!, + style: widget.errorStyle, + textAlign: widget.textAlign, + overflow: TextOverflow.ellipsis, + maxLines: widget.errorMaxLines, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + if (_controller.isDismissed) { + _error = null; + if (widget.helperText != null) { + return _helper = _buildHelper(); + } else { + _helper = null; + return empty; + } + } + + if (_controller.isCompleted) { + _helper = null; + if (_hasError) { + return _error = _buildError(); + } else { + _error = null; + return empty; + } + } + + if (_helper == null && _hasError) { + return _buildError(); + } + + if (_error == null && widget.helperText != null) { + return _buildHelper(); + } + + if (_hasError) { + return Stack( + children: [ + FadeTransition( + opacity: Tween(begin: 1.0, end: 0.0).animate(_controller), + child: _helper, + ), + _buildError(), + ], + ); + } + + if (widget.helperText != null) { + return Stack( + children: [ + _buildHelper(), + FadeTransition( + opacity: _controller, + child: _error, + ), + ], + ); + } + + return empty; + } +} + +/// Defines **how** the floating label should behave. +/// +/// See also: +/// +/// * [RichInputDecoration.floatingLabelBehavior] which defines the behavior for +/// [RichInputDecoration.label] or [RichInputDecoration.labelTextSpan]. +/// * [FloatingLabelAlignment] which defines **where** the floating label +/// should displayed. +enum FloatingLabelBehavior { + /// The label will always be positioned within the content, or hidden. + never, + + /// The label will float when the input is focused, or has content. + auto, + + /// The label will always float above the content. + always, +} + +/// Defines **where** the floating label should be displayed within an +/// [RichInputDecorator]. +/// +/// See also: +/// +/// * [RichInputDecoration.floatingLabelAlignment] which defines the alignment for +/// [RichInputDecoration.label] or [RichInputDecoration.labelTextSpan]. +/// * [FloatingLabelBehavior] which defines **how** the floating label should +/// behave. +@immutable +class FloatingLabelAlignment { + const FloatingLabelAlignment._(this._x) : assert(_x >= -1.0 && _x <= 1.0); + + // -1 denotes start, 0 denotes center, and 1 denotes end. + final double _x; + + /// Align the floating label on the leading edge of the [RichInputDecorator]. + /// + /// For left-to-right text ([TextDirection.ltr]), this is the left edge. + /// + /// For right-to-left text ([TextDirection.rtl]), this is the right edge. + static const FloatingLabelAlignment start = FloatingLabelAlignment._(-1.0); + + /// Aligns the floating label to the center of an [RichInputDecorator]. + static const FloatingLabelAlignment center = FloatingLabelAlignment._(0.0); + + @override + int get hashCode => _x.hashCode; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is FloatingLabelAlignment && _x == other._x; + } + + static String _stringify(double x) { + if (x == -1.0) { + return 'FloatingLabelAlignment.start'; + } + if (x == 0.0) { + return 'FloatingLabelAlignment.center'; + } + return 'FloatingLabelAlignment(x: ${x.toStringAsFixed(1)})'; + } + + @override + String toString() => _stringify(_x); +} + +// Identifies the children of a _RenderDecorationElement. +enum _DecorationSlot { + icon, + input, + label, + hint, + prefix, + suffix, + prefixIcon, + suffixIcon, + helperError, + counter, + container, +} + +// An analog of RichInputDecoration for the _Decorator widget. +@immutable +class _Decoration { + const _Decoration({ + required this.contentPadding, + required this.isCollapsed, + required this.floatingLabelHeight, + required this.floatingLabelProgress, + required this.floatingLabelAlignment, + required this.border, + required this.borderGap, + required this.alignLabelWithHint, + required this.isDense, + required this.visualDensity, + this.icon, + this.input, + this.label, + this.hint, + this.prefix, + this.suffix, + this.prefixIcon, + this.suffixIcon, + this.helperError, + this.counter, + this.container, + }); + + final EdgeInsetsGeometry contentPadding; + final bool isCollapsed; + final double floatingLabelHeight; + final double floatingLabelProgress; + final FloatingLabelAlignment floatingLabelAlignment; + final InputBorder border; + final _InputBorderGap borderGap; + final bool alignLabelWithHint; + final bool? isDense; + final VisualDensity visualDensity; + final Widget? icon; + final Widget? input; + final Widget? label; + final Widget? hint; + final Widget? prefix; + final Widget? suffix; + final Widget? prefixIcon; + final Widget? suffixIcon; + final Widget? helperError; + final Widget? counter; + final Widget? container; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is _Decoration && + other.contentPadding == contentPadding && + other.isCollapsed == isCollapsed && + other.floatingLabelHeight == floatingLabelHeight && + other.floatingLabelProgress == floatingLabelProgress && + other.floatingLabelAlignment == floatingLabelAlignment && + other.border == border && + other.borderGap == borderGap && + other.alignLabelWithHint == alignLabelWithHint && + other.isDense == isDense && + other.visualDensity == visualDensity && + other.icon == icon && + other.input == input && + other.label == label && + other.hint == hint && + other.prefix == prefix && + other.suffix == suffix && + other.prefixIcon == prefixIcon && + other.suffixIcon == suffixIcon && + other.helperError == helperError && + other.counter == counter && + other.container == container; + } + + @override + int get hashCode => Object.hash( + contentPadding, + floatingLabelHeight, + floatingLabelProgress, + floatingLabelAlignment, + border, + borderGap, + alignLabelWithHint, + isDense, + visualDensity, + icon, + input, + label, + hint, + prefix, + suffix, + prefixIcon, + suffixIcon, + helperError, + counter, + container, + ); +} + +// A container for the layout values computed by _RenderDecoration._layout. +// These values are used by _RenderDecoration.performLayout to position +// all of the renderer children of a _RenderDecoration. +class _RenderDecorationLayout { + const _RenderDecorationLayout({ + required this.boxToBaseline, + required this.inputBaseline, // for InputBorderType.underline + required this.outlineBaseline, // for InputBorderType.outline + required this.subtextBaseline, + required this.containerHeight, + required this.subtextHeight, + }); + + final Map boxToBaseline; + final double inputBaseline; + final double outlineBaseline; + final double subtextBaseline; // helper/error counter + final double containerHeight; + final double subtextHeight; +} + +// The workhorse: layout and paint a _Decorator widget's _Decoration. +class _RenderDecoration extends RenderBox + with SlottedContainerRenderObjectMixin<_DecorationSlot, RenderBox> { + _RenderDecoration({ + required _Decoration decoration, + required TextDirection textDirection, + required TextBaseline textBaseline, + required bool isFocused, + required bool expands, + required bool material3, + TextAlignVertical? textAlignVertical, + }) : _decoration = decoration, + _textDirection = textDirection, + _textBaseline = textBaseline, + _textAlignVertical = textAlignVertical, + _isFocused = isFocused, + _expands = expands, + _material3 = material3; + + static const double subtextGap = 8.0; + + RenderBox? get icon => childForSlot(_DecorationSlot.icon); + RenderBox? get input => childForSlot(_DecorationSlot.input); + RenderBox? get label => childForSlot(_DecorationSlot.label); + RenderBox? get hint => childForSlot(_DecorationSlot.hint); + RenderBox? get prefix => childForSlot(_DecorationSlot.prefix); + RenderBox? get suffix => childForSlot(_DecorationSlot.suffix); + RenderBox? get prefixIcon => childForSlot(_DecorationSlot.prefixIcon); + RenderBox? get suffixIcon => childForSlot(_DecorationSlot.suffixIcon); + RenderBox? get helperError => childForSlot(_DecorationSlot.helperError); + RenderBox? get counter => childForSlot(_DecorationSlot.counter); + RenderBox? get container => childForSlot(_DecorationSlot.container); + + // The returned list is ordered for hit testing. + @override + Iterable get children { + return [ + if (icon != null) icon!, + if (input != null) input!, + if (prefixIcon != null) prefixIcon!, + if (suffixIcon != null) suffixIcon!, + if (prefix != null) prefix!, + if (suffix != null) suffix!, + if (label != null) label!, + if (hint != null) hint!, + if (helperError != null) helperError!, + if (counter != null) counter!, + if (container != null) container!, + ]; + } + + _Decoration get decoration => _decoration; + _Decoration _decoration; + set decoration(_Decoration value) { + if (_decoration == value) { + return; + } + _decoration = value; + markNeedsLayout(); + } + + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + if (_textDirection == value) { + return; + } + _textDirection = value; + markNeedsLayout(); + } + + TextBaseline get textBaseline => _textBaseline; + TextBaseline _textBaseline; + set textBaseline(TextBaseline value) { + if (_textBaseline == value) { + return; + } + _textBaseline = value; + markNeedsLayout(); + } + + TextAlignVertical get _defaultTextAlignVertical => + _isOutlineAligned ? TextAlignVertical.center : TextAlignVertical.top; + TextAlignVertical get textAlignVertical => + _textAlignVertical ?? _defaultTextAlignVertical; + TextAlignVertical? _textAlignVertical; + set textAlignVertical(TextAlignVertical? value) { + if (_textAlignVertical == value) { + return; + } + // No need to relayout if the effective value is still the same. + if (textAlignVertical.y == (value?.y ?? _defaultTextAlignVertical.y)) { + _textAlignVertical = value; + return; + } + _textAlignVertical = value; + markNeedsLayout(); + } + + bool get isFocused => _isFocused; + bool _isFocused; + set isFocused(bool value) { + if (_isFocused == value) { + return; + } + _isFocused = value; + markNeedsSemanticsUpdate(); + } + + bool get expands => _expands; + bool _expands = false; + set expands(bool value) { + if (_expands == value) { + return; + } + _expands = value; + markNeedsLayout(); + } + + bool get material3 => _material3; + bool _material3 = false; + set material3(bool value) { + if (_material3 == value) { + return; + } + _material3 = value; + markNeedsLayout(); + } + + // Indicates that the decoration should be aligned to accommodate an outline + // border. + bool get _isOutlineAligned { + return !decoration.isCollapsed && decoration.border.isOutline; + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + if (icon != null) { + visitor(icon!); + } + if (prefix != null) { + visitor(prefix!); + } + if (prefixIcon != null) { + visitor(prefixIcon!); + } + + if (label != null) { + visitor(label!); + } + if (hint != null) { + if (isFocused) { + visitor(hint!); + } else if (label == null) { + visitor(hint!); + } + } + + if (input != null) { + visitor(input!); + } + if (suffixIcon != null) { + visitor(suffixIcon!); + } + if (suffix != null) { + visitor(suffix!); + } + if (container != null) { + visitor(container!); + } + if (helperError != null) { + visitor(helperError!); + } + if (counter != null) { + visitor(counter!); + } + } + + @override + bool get sizedByParent => false; + + static double _minWidth(RenderBox? box, double height) { + return box == null ? 0.0 : box.getMinIntrinsicWidth(height); + } + + static double _maxWidth(RenderBox? box, double height) { + return box == null ? 0.0 : box.getMaxIntrinsicWidth(height); + } + + static double _minHeight(RenderBox? box, double width) { + return box == null ? 0.0 : box.getMinIntrinsicHeight(width); + } + + static Size _boxSize(RenderBox? box) => box == null ? Size.zero : box.size; + + static BoxParentData _boxParentData(RenderBox box) => + box.parentData! as BoxParentData; + + EdgeInsets get contentPadding => decoration.contentPadding as EdgeInsets; + + // Lay out the given box if needed, and return its baseline. + double _layoutLineBox(RenderBox? box, BoxConstraints constraints) { + if (box == null) { + return 0.0; + } + box.layout(constraints, parentUsesSize: true); + // Since internally, all layout is performed against the alphabetic baseline, + // (eg, ascents/descents are all relative to alphabetic, even if the font is + // an ideographic or hanging font), we should always obtain the reference + // baseline from the alphabetic baseline. The ideographic baseline is for + // use post-layout and is derived from the alphabetic baseline combined with + // the font metrics. + final double baseline = box.getDistanceToBaseline(TextBaseline.alphabetic)!; + + assert(() { + if (baseline >= 0) { + return true; + } + throw FlutterError.fromParts([ + ErrorSummary( + "One of RichInputDecorator's children reported a negative baseline offset."), + ErrorDescription( + '${box.runtimeType}, of size ${box.size}, reported a negative ' + 'alphabetic baseline of $baseline.', + ), + ]); + }()); + return baseline; + } + + // Returns a value used by performLayout to position all of the renderers. + // This method applies layout to all of the renderers except the container. + // For convenience, the container is laid out in performLayout(). + _RenderDecorationLayout _layout(BoxConstraints layoutConstraints) { + assert( + layoutConstraints.maxWidth < double.infinity, + 'An RichInputDecorator, which is typically created by a TextField, cannot ' + 'have an unbounded width.\n' + 'This happens when the parent widget does not provide a finite width ' + 'constraint. For example, if the RichInputDecorator is contained by a Row, ' + 'then its width must be constrained. An Expanded widget or a SizedBox ' + 'can be used to constrain the width of the RichInputDecorator or the ' + 'TextField that contains it.', + ); + + // Margin on each side of subtext (counter and helperError) + final Map boxToBaseline = {}; + final BoxConstraints boxConstraints = layoutConstraints.loosen(); + + // Layout all the widgets used by RichInputDecorator + boxToBaseline[icon] = _layoutLineBox(icon, boxConstraints); + final BoxConstraints containerConstraints = boxConstraints.copyWith( + maxWidth: boxConstraints.maxWidth - _boxSize(icon).width, + ); + boxToBaseline[prefixIcon] = + _layoutLineBox(prefixIcon, containerConstraints); + boxToBaseline[suffixIcon] = + _layoutLineBox(suffixIcon, containerConstraints); + final BoxConstraints contentConstraints = containerConstraints.copyWith( + maxWidth: math.max( + 0.0, containerConstraints.maxWidth - contentPadding.horizontal), + ); + boxToBaseline[prefix] = _layoutLineBox(prefix, contentConstraints); + boxToBaseline[suffix] = _layoutLineBox(suffix, contentConstraints); + + final double inputWidth = math.max( + 0.0, + constraints.maxWidth - + (_boxSize(icon).width + + (prefixIcon != null + ? 0 + : (textDirection == TextDirection.ltr + ? contentPadding.left + : contentPadding.right)) + + _boxSize(prefixIcon).width + + _boxSize(prefix).width + + _boxSize(suffix).width + + _boxSize(suffixIcon).width + + (suffixIcon != null + ? 0 + : (textDirection == TextDirection.ltr + ? contentPadding.right + : contentPadding.left))), + ); + // Increase the available width for the label when it is scaled down. + final double invertedLabelScale = lerpDouble( + 1.00, 1 / _kFinalLabelScale, decoration.floatingLabelProgress)!; + double suffixIconWidth = _boxSize(suffixIcon).width; + if (decoration.border.isOutline) { + suffixIconWidth = + lerpDouble(suffixIconWidth, 0.0, decoration.floatingLabelProgress)!; + } + final double labelWidth = math.max( + 0.0, + constraints.maxWidth - + (_boxSize(icon).width + + contentPadding.left + + _boxSize(prefixIcon).width + + suffixIconWidth + + contentPadding.right), + ); + boxToBaseline[label] = _layoutLineBox( + label, + boxConstraints.copyWith(maxWidth: labelWidth * invertedLabelScale), + ); + boxToBaseline[hint] = _layoutLineBox( + hint, + boxConstraints.copyWith(minWidth: inputWidth, maxWidth: inputWidth), + ); + boxToBaseline[counter] = _layoutLineBox(counter, contentConstraints); + + // The helper or error text can occupy the full width less the space + // occupied by the icon and counter. + boxToBaseline[helperError] = _layoutLineBox( + helperError, + contentConstraints.copyWith( + maxWidth: math.max( + 0.0, contentConstraints.maxWidth - _boxSize(counter).width), + ), + ); + + // The height of the input needs to accommodate label above and counter and + // helperError below, when they exist. + final double labelHeight = + label == null ? 0 : decoration.floatingLabelHeight; + final double topHeight = decoration.border.isOutline + ? math.max(labelHeight - boxToBaseline[label]!, 0) + : labelHeight; + final double counterHeight = + counter == null ? 0 : boxToBaseline[counter]! + subtextGap; + final bool helperErrorExists = + helperError?.size != null && helperError!.size.height > 0; + final double helperErrorHeight = + !helperErrorExists ? 0 : helperError!.size.height + subtextGap; + final double bottomHeight = math.max( + counterHeight, + helperErrorHeight, + ); + final Offset densityOffset = decoration.visualDensity.baseSizeAdjustment; + boxToBaseline[input] = _layoutLineBox( + input, + boxConstraints + .deflate(EdgeInsets.only( + top: contentPadding.top + topHeight + densityOffset.dy / 2, + bottom: contentPadding.bottom + bottomHeight + densityOffset.dy / 2, + )) + .copyWith( + minWidth: inputWidth, + maxWidth: inputWidth, + ), + ); + + // The field can be occupied by a hint or by the input itself + final double hintHeight = hint?.size.height ?? 0; + final double inputDirectHeight = input?.size.height ?? 0; + final double inputHeight = math.max(hintHeight, inputDirectHeight); + final double inputInternalBaseline = math.max( + boxToBaseline[input]!, + boxToBaseline[hint]!, + ); + + // Calculate the amount that prefix/suffix affects height above and below + // the input. + final double prefixHeight = prefix?.size.height ?? 0; + final double suffixHeight = suffix?.size.height ?? 0; + final double fixHeight = math.max( + boxToBaseline[prefix]!, + boxToBaseline[suffix]!, + ); + final double fixAboveInput = math.max(0, fixHeight - inputInternalBaseline); + final double fixBelowBaseline = math.max( + prefixHeight - boxToBaseline[prefix]!, + suffixHeight - boxToBaseline[suffix]!, + ); + // TODO(justinmc): fixBelowInput should have no effect when there is no + // prefix/suffix below the input. + // https://github.com/flutter/flutter/issues/66050 + final double fixBelowInput = math.max( + 0, + fixBelowBaseline - (inputHeight - inputInternalBaseline), + ); + + // Calculate the height of the input text container. + final double prefixIconHeight = prefixIcon?.size.height ?? 0; + final double suffixIconHeight = suffixIcon?.size.height ?? 0; + final double fixIconHeight = math.max(prefixIconHeight, suffixIconHeight); + final double contentHeight = math.max( + fixIconHeight, + topHeight + + contentPadding.top + + fixAboveInput + + inputHeight + + fixBelowInput + + contentPadding.bottom + + densityOffset.dy, + ); + final double minContainerHeight = + decoration.isDense! || decoration.isCollapsed || expands + ? 0.0 + : kMinInteractiveDimension; + final double maxContainerHeight = + math.max(0.0, boxConstraints.maxHeight - bottomHeight); + final double containerHeight = expands + ? maxContainerHeight + : math.min( + math.max(contentHeight, minContainerHeight), maxContainerHeight); + + // Ensure the text is vertically centered in cases where the content is + // shorter than kMinInteractiveDimension. + final double interactiveAdjustment = minContainerHeight > contentHeight + ? (minContainerHeight - contentHeight) / 2.0 + : 0.0; + + // Try to consider the prefix/suffix as part of the text when aligning it. + // If the prefix/suffix overflows however, allow it to extend outside of the + // input and align the remaining part of the text and prefix/suffix. + final double overflow = math.max(0, contentHeight - maxContainerHeight); + // Map textAlignVertical from -1:1 to 0:1 so that it can be used to scale + // the baseline from its minimum to maximum values. + final double textAlignVerticalFactor = (textAlignVertical.y + 1.0) / 2.0; + // Adjust to try to fit top overflow inside the input on an inverse scale of + // textAlignVertical, so that top aligned text adjusts the most and bottom + // aligned text doesn't adjust at all. + final double baselineAdjustment = + fixAboveInput - overflow * (1 - textAlignVerticalFactor); + + // The baselines that will be used to draw the actual input text content. + final double topInputBaseline = contentPadding.top + + topHeight + + inputInternalBaseline + + baselineAdjustment + + interactiveAdjustment + + densityOffset.dy / 2.0; + final double maxContentHeight = containerHeight - + contentPadding.vertical - + topHeight - + densityOffset.dy; + final double alignableHeight = fixAboveInput + inputHeight + fixBelowInput; + final double maxVerticalOffset = maxContentHeight - alignableHeight; + final double textAlignVerticalOffset = + maxVerticalOffset * textAlignVerticalFactor; + final double inputBaseline = topInputBaseline + textAlignVerticalOffset; + + // The three main alignments for the baseline when an outline is present are + // + // * top (-1.0): topmost point considering padding. + // * center (0.0): the absolute center of the input ignoring padding but + // accommodating the border and floating label. + // * bottom (1.0): bottommost point considering padding. + // + // That means that if the padding is uneven, center is not the exact + // midpoint of top and bottom. To account for this, the above center and + // below center alignments are interpolated independently. + final double outlineCenterBaseline = inputInternalBaseline + + baselineAdjustment / 2.0 + + (containerHeight - (2.0 + inputHeight)) / 2.0; + final double outlineTopBaseline = topInputBaseline; + final double outlineBottomBaseline = topInputBaseline + maxVerticalOffset; + final double outlineBaseline = _interpolateThree( + outlineTopBaseline, + outlineCenterBaseline, + outlineBottomBaseline, + textAlignVertical, + ); + + // Find the positions of the text below the input when it exists. + double subtextCounterBaseline = 0; + double subtextHelperBaseline = 0; + double subtextCounterHeight = 0; + double subtextHelperHeight = 0; + if (counter != null) { + subtextCounterBaseline = + containerHeight + subtextGap + boxToBaseline[counter]!; + subtextCounterHeight = counter!.size.height + subtextGap; + } + if (helperErrorExists) { + subtextHelperBaseline = + containerHeight + subtextGap + boxToBaseline[helperError]!; + subtextHelperHeight = helperErrorHeight; + } + final double subtextBaseline = math.max( + subtextCounterBaseline, + subtextHelperBaseline, + ); + final double subtextHeight = math.max( + subtextCounterHeight, + subtextHelperHeight, + ); + + return _RenderDecorationLayout( + boxToBaseline: boxToBaseline, + containerHeight: containerHeight, + inputBaseline: inputBaseline, + outlineBaseline: outlineBaseline, + subtextBaseline: subtextBaseline, + subtextHeight: subtextHeight, + ); + } + + // Interpolate between three stops using textAlignVertical. This is used to + // calculate the outline baseline, which ignores padding when the alignment is + // middle. When the alignment is less than zero, it interpolates between the + // centered text box's top and the top of the content padding. When the + // alignment is greater than zero, it interpolates between the centered box's + // top and the position that would align the bottom of the box with the bottom + // padding. + double _interpolateThree(double begin, double middle, double end, + TextAlignVertical textAlignVertical) { + if (textAlignVertical.y <= 0) { + // It's possible for begin, middle, and end to not be in order because of + // excessive padding. Those cases are handled by using middle. + if (begin >= middle) { + return middle; + } + // Do a standard linear interpolation on the first half, between begin and + // middle. + final double t = textAlignVertical.y + 1; + return begin + (middle - begin) * t; + } + + if (middle >= end) { + return middle; + } + // Do a standard linear interpolation on the second half, between middle and + // end. + final double t = textAlignVertical.y; + return middle + (end - middle) * t; + } + + @override + double computeMinIntrinsicWidth(double height) { + return _minWidth(icon, height) + + contentPadding.left + + _minWidth(prefixIcon, height) + + _minWidth(prefix, height) + + math.max(_minWidth(input, height), _minWidth(hint, height)) + + _minWidth(suffix, height) + + _minWidth(suffixIcon, height) + + contentPadding.right; + } + + @override + double computeMaxIntrinsicWidth(double height) { + return _maxWidth(icon, height) + + contentPadding.left + + _maxWidth(prefixIcon, height) + + _maxWidth(prefix, height) + + math.max(_maxWidth(input, height), _maxWidth(hint, height)) + + _maxWidth(suffix, height) + + _maxWidth(suffixIcon, height) + + contentPadding.right; + } + + double _lineHeight(double width, List boxes) { + double height = 0.0; + for (final RenderBox? box in boxes) { + if (box == null) { + continue; + } + height = math.max(_minHeight(box, width), height); + } + return height; + // TODO(hansmuller): this should compute the overall line height for the + // boxes when they've been baseline-aligned. + // See https://github.com/flutter/flutter/issues/13715 + } + + @override + double computeMinIntrinsicHeight(double width) { + final double iconHeight = _minHeight(icon, width); + final double iconWidth = _minWidth(icon, iconHeight); + + width = math.max(width - iconWidth, 0.0); + + final double prefixIconHeight = _minHeight(prefixIcon, width); + final double prefixIconWidth = _minWidth(prefixIcon, prefixIconHeight); + + final double suffixIconHeight = _minHeight(suffixIcon, width); + final double suffixIconWidth = _minWidth(suffixIcon, suffixIconHeight); + + width = math.max(width - contentPadding.horizontal, 0.0); + + final double counterHeight = _minHeight(counter, width); + final double counterWidth = _minWidth(counter, counterHeight); + + final double helperErrorAvailableWidth = + math.max(width - counterWidth, 0.0); + final double helperErrorHeight = + _minHeight(helperError, helperErrorAvailableWidth); + double subtextHeight = math.max(counterHeight, helperErrorHeight); + if (subtextHeight > 0.0) { + subtextHeight += subtextGap; + } + + final double prefixHeight = _minHeight(prefix, width); + final double prefixWidth = _minWidth(prefix, prefixHeight); + + final double suffixHeight = _minHeight(suffix, width); + final double suffixWidth = _minWidth(suffix, suffixHeight); + + final double availableInputWidth = math.max( + width - prefixWidth - suffixWidth - prefixIconWidth - suffixIconWidth, + 0.0); + final double inputHeight = + _lineHeight(availableInputWidth, [input, hint]); + final double inputMaxHeight = + [inputHeight, prefixHeight, suffixHeight].reduce(math.max); + + final Offset densityOffset = decoration.visualDensity.baseSizeAdjustment; + final double contentHeight = contentPadding.top + + (label == null ? 0.0 : decoration.floatingLabelHeight) + + inputMaxHeight + + contentPadding.bottom + + densityOffset.dy; + final double containerHeight = [ + iconHeight, + contentHeight, + prefixIconHeight, + suffixIconHeight + ].reduce(math.max); + final double minContainerHeight = + decoration.isDense! || expands ? 0.0 : kMinInteractiveDimension; + return math.max(containerHeight, minContainerHeight) + subtextHeight; + } + + @override + double computeMaxIntrinsicHeight(double width) { + return computeMinIntrinsicHeight(width); + } + + @override + double computeDistanceToActualBaseline(TextBaseline baseline) { + return _boxParentData(input!).offset.dy + + (input?.computeDistanceToActualBaseline(baseline) ?? 0.0); + } + + // Records where the label was painted. + Matrix4? _labelTransform; + + @override + Size computeDryLayout(BoxConstraints constraints) { + assert(debugCannotComputeDryLayout( + reason: + 'Layout requires baseline metrics, which are only available after a full layout.', + )); + return Size.zero; + } + + ChildSemanticsConfigurationsResult _childSemanticsConfigurationDelegate( + List childConfigs) { + final ChildSemanticsConfigurationsResultBuilder builder = + ChildSemanticsConfigurationsResultBuilder(); + List? prefixMergeGroup; + List? suffixMergeGroup; + for (final SemanticsConfiguration childConfig in childConfigs) { + if (childConfig + .tagsChildrenWith(_InputDecoratorState._kPrefixSemanticsTag)) { + prefixMergeGroup ??= []; + prefixMergeGroup.add(childConfig); + } else if (childConfig + .tagsChildrenWith(_InputDecoratorState._kSuffixSemanticsTag)) { + suffixMergeGroup ??= []; + suffixMergeGroup.add(childConfig); + } else { + builder.markAsMergeUp(childConfig); + } + } + if (prefixMergeGroup != null) { + builder.markAsSiblingMergeGroup(prefixMergeGroup); + } + if (suffixMergeGroup != null) { + builder.markAsSiblingMergeGroup(suffixMergeGroup); + } + return builder.build(); + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + config.childConfigurationsDelegate = _childSemanticsConfigurationDelegate; + } + + @override + void performLayout() { + final BoxConstraints constraints = this.constraints; + _labelTransform = null; + final _RenderDecorationLayout layout = _layout(constraints); + + final double overallWidth = constraints.maxWidth; + final double overallHeight = layout.containerHeight + layout.subtextHeight; + + final RenderBox? container = this.container; + if (container != null) { + final BoxConstraints containerConstraints = BoxConstraints.tightFor( + height: layout.containerHeight, + width: overallWidth - _boxSize(icon).width, + ); + container.layout(containerConstraints, parentUsesSize: true); + final double x; + switch (textDirection) { + case TextDirection.rtl: + x = 0.0; + case TextDirection.ltr: + x = _boxSize(icon).width; + } + _boxParentData(container).offset = Offset(x, 0.0); + } + + late double height; + double centerLayout(RenderBox box, double x) { + _boxParentData(box).offset = Offset(x, (height - box.size.height) / 2.0); + return box.size.width; + } + + late double baseline; + double baselineLayout(RenderBox box, double x) { + _boxParentData(box).offset = + Offset(x, baseline - layout.boxToBaseline[box]!); + return box.size.width; + } + + final double left = contentPadding.left; + final double right = overallWidth - contentPadding.right; + + height = layout.containerHeight; + baseline = + _isOutlineAligned ? layout.outlineBaseline : layout.inputBaseline; + + if (icon != null) { + final double x; + switch (textDirection) { + case TextDirection.rtl: + x = overallWidth - icon!.size.width; + case TextDirection.ltr: + x = 0.0; + } + centerLayout(icon!, x); + } + + switch (textDirection) { + case TextDirection.rtl: + { + double start = right - _boxSize(icon).width; + double end = left; + if (prefixIcon != null) { + start += contentPadding.right; + start -= centerLayout(prefixIcon!, start - prefixIcon!.size.width); + } + if (label != null) { + if (decoration.alignLabelWithHint) { + baselineLayout(label!, start - label!.size.width); + } else { + centerLayout(label!, start - label!.size.width); + } + } + if (prefix != null) { + start -= baselineLayout(prefix!, start - prefix!.size.width); + } + if (input != null) { + baselineLayout(input!, start - input!.size.width); + } + if (hint != null) { + baselineLayout(hint!, start - hint!.size.width); + } + if (suffixIcon != null) { + end -= contentPadding.left; + end += centerLayout(suffixIcon!, end); + } + if (suffix != null) { + end += baselineLayout(suffix!, end); + } + break; + } + case TextDirection.ltr: + { + double start = left + _boxSize(icon).width; + double end = right; + if (prefixIcon != null) { + start -= contentPadding.left; + start += centerLayout(prefixIcon!, start); + } + if (label != null) { + if (decoration.alignLabelWithHint) { + baselineLayout(label!, start); + } else { + centerLayout(label!, start); + } + } + if (prefix != null) { + start += baselineLayout(prefix!, start); + } + if (input != null) { + baselineLayout(input!, start); + } + if (hint != null) { + baselineLayout(hint!, start); + } + if (suffixIcon != null) { + end += contentPadding.right; + end -= centerLayout(suffixIcon!, end - suffixIcon!.size.width); + } + if (suffix != null) { + end -= baselineLayout(suffix!, end - suffix!.size.width); + } + break; + } + } + + if (helperError != null || counter != null) { + height = layout.subtextHeight; + baseline = layout.subtextBaseline; + + switch (textDirection) { + case TextDirection.rtl: + if (helperError != null) { + baselineLayout(helperError!, + right - helperError!.size.width - _boxSize(icon).width); + } + if (counter != null) { + baselineLayout(counter!, left); + } + case TextDirection.ltr: + if (helperError != null) { + baselineLayout(helperError!, left + _boxSize(icon).width); + } + if (counter != null) { + baselineLayout(counter!, right - counter!.size.width); + } + } + } + + if (label != null) { + final double labelX = _boxParentData(label!).offset.dx; + // +1 shifts the range of x from (-1.0, 1.0) to (0.0, 2.0). + final double floatAlign = decoration.floatingLabelAlignment._x + 1; + final double floatWidth = _boxSize(label).width * _kFinalLabelScale; + // When floating label is centered, its x is relative to + // _BorderContainer's x and is independent of label's x. + switch (textDirection) { + case TextDirection.rtl: + double offsetToPrefixIcon = 0.0; + if (prefixIcon != null && !decoration.alignLabelWithHint) { + offsetToPrefixIcon = + material3 ? _boxSize(prefixIcon).width - left : 0; + } + decoration.borderGap.start = lerpDouble( + labelX + _boxSize(label).width + offsetToPrefixIcon, + _boxSize(container).width / 2.0 + floatWidth / 2.0, + floatAlign); + + case TextDirection.ltr: + // The value of _InputBorderGap.start is relative to the origin of the + // _BorderContainer which is inset by the icon's width. Although, when + // floating label is centered, it's already relative to _BorderContainer. + double offsetToPrefixIcon = 0.0; + if (prefixIcon != null && !decoration.alignLabelWithHint) { + offsetToPrefixIcon = + material3 ? (-_boxSize(prefixIcon).width + left) : 0; + } + decoration.borderGap.start = lerpDouble( + labelX - _boxSize(icon).width + offsetToPrefixIcon, + _boxSize(container).width / 2.0 - floatWidth / 2.0, + floatAlign); + } + decoration.borderGap.extent = label!.size.width * _kFinalLabelScale; + } else { + decoration.borderGap.start = null; + decoration.borderGap.extent = 0.0; + } + + size = constraints.constrain(Size(overallWidth, overallHeight)); + assert(size.width == constraints.constrainWidth(overallWidth)); + assert(size.height == constraints.constrainHeight(overallHeight)); + } + + void _paintLabel(PaintingContext context, Offset offset) { + context.paintChild(label!, offset); + } + + @override + void paint(PaintingContext context, Offset offset) { + void doPaint(RenderBox? child) { + if (child != null) { + context.paintChild(child, _boxParentData(child).offset + offset); + } + } + + doPaint(container); + + if (label != null) { + final Offset labelOffset = _boxParentData(label!).offset; + final double labelHeight = _boxSize(label).height; + final double labelWidth = _boxSize(label).width; + // +1 shifts the range of x from (-1.0, 1.0) to (0.0, 2.0). + final double floatAlign = decoration.floatingLabelAlignment._x + 1; + final double floatWidth = labelWidth * _kFinalLabelScale; + final double borderWeight = decoration.border.borderSide.width; + final double t = decoration.floatingLabelProgress; + // The center of the outline border label ends up a little below the + // center of the top border line. + final bool isOutlineBorder = decoration.border.isOutline; + // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028 + // Center the scaled label relative to the border. + final double floatingY = isOutlineBorder + ? (-labelHeight * _kFinalLabelScale) / 2.0 + borderWeight / 2.0 + : contentPadding.top; + final double scale = lerpDouble(1.0, _kFinalLabelScale, t)!; + final double centeredFloatX = _boxParentData(container!).offset.dx + + _boxSize(container).width / 2.0 - + floatWidth / 2.0; + final double startX; + double floatStartX; + switch (textDirection) { + case TextDirection.rtl: // origin is on the right + startX = labelOffset.dx + labelWidth * (1.0 - scale); + floatStartX = startX; + if (prefixIcon != null && + !decoration.alignLabelWithHint && + isOutlineBorder) { + floatStartX += material3 + ? _boxSize(prefixIcon).width - contentPadding.left + : 0.0; + } + case TextDirection.ltr: // origin on the left + startX = labelOffset.dx; + floatStartX = startX; + if (prefixIcon != null && + !decoration.alignLabelWithHint && + isOutlineBorder) { + floatStartX += material3 + ? -_boxSize(prefixIcon).width + contentPadding.left + : 0.0; + } + } + final double floatEndX = + lerpDouble(floatStartX, centeredFloatX, floatAlign)!; + final double dx = lerpDouble(startX, floatEndX, t)!; + final double dy = lerpDouble(0.0, floatingY - labelOffset.dy, t)!; + _labelTransform = Matrix4.identity() + ..translate(dx, labelOffset.dy + dy) + ..scale(scale); + layer = context.pushTransform( + needsCompositing, + offset, + _labelTransform!, + _paintLabel, + oldLayer: layer as TransformLayer?, + ); + } else { + layer = null; + } + + doPaint(icon); + doPaint(prefix); + doPaint(suffix); + doPaint(prefixIcon); + doPaint(suffixIcon); + doPaint(hint); + doPaint(input); + doPaint(helperError); + doPaint(counter); + } + + @override + bool hitTestSelf(Offset position) => true; + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + for (final RenderBox child in children) { + // The label must be handled specially since we've transformed it. + final Offset offset = _boxParentData(child).offset; + final bool isHit = result.addWithPaintOffset( + offset: offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + assert(transformed == position - offset); + return child.hitTest(result, position: transformed); + }, + ); + if (isHit) { + return true; + } + } + return false; + } + + @override + void applyPaintTransform(RenderObject child, Matrix4 transform) { + if (child == label && _labelTransform != null) { + final Offset labelOffset = _boxParentData(label!).offset; + transform + ..multiply(_labelTransform!) + ..translate(-labelOffset.dx, -labelOffset.dy); + } + super.applyPaintTransform(child, transform); + } +} + +class _Decorator + extends SlottedMultiChildRenderObjectWidget<_DecorationSlot, RenderBox> { + const _Decorator({ + required this.textAlignVertical, + required this.decoration, + required this.textDirection, + required this.textBaseline, + required this.isFocused, + required this.expands, + }); + + final _Decoration decoration; + final TextDirection textDirection; + final TextBaseline textBaseline; + final TextAlignVertical? textAlignVertical; + final bool isFocused; + final bool expands; + + @override + Iterable<_DecorationSlot> get slots => _DecorationSlot.values; + + @override + Widget? childForSlot(_DecorationSlot slot) { + switch (slot) { + case _DecorationSlot.icon: + return decoration.icon; + case _DecorationSlot.input: + return decoration.input; + case _DecorationSlot.label: + return decoration.label; + case _DecorationSlot.hint: + return decoration.hint; + case _DecorationSlot.prefix: + return decoration.prefix; + case _DecorationSlot.suffix: + return decoration.suffix; + case _DecorationSlot.prefixIcon: + return decoration.prefixIcon; + case _DecorationSlot.suffixIcon: + return decoration.suffixIcon; + case _DecorationSlot.helperError: + return decoration.helperError; + case _DecorationSlot.counter: + return decoration.counter; + case _DecorationSlot.container: + return decoration.container; + } + } + + @override + _RenderDecoration createRenderObject(BuildContext context) { + return _RenderDecoration( + decoration: decoration, + textDirection: textDirection, + textBaseline: textBaseline, + textAlignVertical: textAlignVertical, + isFocused: isFocused, + expands: expands, + material3: Theme.of(context).useMaterial3, + ); + } + + @override + void updateRenderObject( + BuildContext context, _RenderDecoration renderObject) { + renderObject + ..decoration = decoration + ..expands = expands + ..isFocused = isFocused + ..textAlignVertical = textAlignVertical + ..textBaseline = textBaseline + ..textDirection = textDirection; + } +} + +class _AffixText extends StatelessWidget { + const _AffixText({ + required this.labelIsFloating, + this.text, + this.style, + this.child, + this.semanticsSortKey, + required this.semanticsTag, + }); + + final bool labelIsFloating; + final String? text; + final TextStyle? style; + final Widget? child; + final SemanticsSortKey? semanticsSortKey; + final SemanticsTag semanticsTag; + + @override + Widget build(BuildContext context) { + return DefaultTextStyle.merge( + style: style, + child: AnimatedOpacity( + duration: _kTransitionDuration, + curve: _kTransitionCurve, + opacity: labelIsFloating ? 1.0 : 0.0, + child: Semantics( + sortKey: semanticsSortKey, + tagForChildren: semanticsTag, + child: child ?? (text == null ? null : Text(text!, style: style)), + ), + ), + ); + } +} + +/// Defines the appearance of a Material Design text field. +/// +/// [RichInputDecorator] displays the visual elements of a Material Design text +/// field around its input [child]. The visual elements themselves are defined +/// by an [RichInputDecoration] object and their layout and appearance depend +/// on the `baseStyle`, `textAlign`, `isFocused`, and `isEmpty` parameters. +/// +/// [TextField] uses this widget to decorate its [EditableText] child. +/// +/// [RichInputDecorator] can be used to create widgets that look and behave like a +/// [TextField] but support other kinds of input. +/// +/// Requires one of its ancestors to be a [Material] widget. The [child] widget, +/// as well as the decorative widgets specified in [decoration], must have +/// non-negative baselines. +/// +/// See also: +/// +/// * [TextField], which uses an [RichInputDecorator] to display a border, +/// labels, and icons, around its [EditableText] child. +/// * [Decoration] and [DecoratedBox], for drawing arbitrary decorations +/// around other widgets. +class RichInputDecorator extends StatefulWidget { + /// Creates a widget that displays a border, labels, and icons, + /// for a [TextField]. + /// + /// The [isFocused], [isHovering], [expands], and [isEmpty] arguments must not + /// be null. + const RichInputDecorator({ + super.key, + required this.decoration, + this.baseStyle, + this.textAlign, + this.textAlignVertical, + this.isFocused = false, + this.isHovering = false, + this.expands = false, + this.isEmpty = false, + this.child, + }); + + /// The text and styles to use when decorating the child. + /// + /// Null [RichInputDecoration] properties are initialized with the corresponding + /// values from [ThemeData.inputDecorationTheme]. + final RichInputDecoration decoration; + + /// The style on which to base the label, hint, counter, and error styles + /// if the [decoration] does not provide explicit styles. + /// + /// If null, [baseStyle] defaults to the `titleMedium` style from the + /// current [Theme], see [ThemeData.textTheme]. + /// + /// The [TextStyle.textBaseline] of the [baseStyle] is used to determine + /// the baseline used for text alignment. + final TextStyle? baseStyle; + + /// How the text in the decoration should be aligned horizontally. + final TextAlign? textAlign; + + /// {@template flutter.material.RichInputDecorator.textAlignVertical} + /// How the text should be aligned vertically. + /// + /// Determines the alignment of the baseline within the available space of + /// the input (typically a TextField). For example, TextAlignVertical.top will + /// place the baseline such that the text, and any attached decoration like + /// prefix and suffix, is as close to the top of the input as possible without + /// overflowing. The heights of the prefix and suffix are similarly included + /// for other alignment values. If the height is greater than the height + /// available, then the prefix and suffix will be allowed to overflow first + /// before the text scrolls. + /// {@endtemplate} + final TextAlignVertical? textAlignVertical; + + /// Whether the input field has focus. + /// + /// Determines the position of the label text and the color and weight of the + /// border. + /// + /// Defaults to false. + /// + /// See also: + /// + /// * [RichInputDecoration.hoverColor], which is also blended into the focus + /// color and fill color when the [isHovering] is true to produce the final + /// color. + final bool isFocused; + + /// Whether the input field is being hovered over by a mouse pointer. + /// + /// Determines the container fill color, which is a blend of + /// [RichInputDecoration.hoverColor] with [RichInputDecoration.fillColor] when + /// true, and [RichInputDecoration.fillColor] when not. + /// + /// Defaults to false. + final bool isHovering; + + /// If true, the height of the input field will be as large as possible. + /// + /// If wrapped in a widget that constrains its child's height, like Expanded + /// or SizedBox, the input field will only be affected if [expands] is set to + /// true. + /// + /// See [TextField.minLines] and [TextField.maxLines] for related ways to + /// affect the height of an input. When [expands] is true, both must be null + /// in order to avoid ambiguity in determining the height. + /// + /// Defaults to false. + final bool expands; + + /// Whether the input field is empty. + /// + /// Determines the position of the label text and whether to display the hint + /// text. + /// + /// Defaults to false. + final bool isEmpty; + + /// The widget below this widget in the tree. + /// + /// Typically an [EditableText], [DropdownButton], or [InkWell]. + final Widget? child; + + /// Whether the label needs to get out of the way of the input, either by + /// floating or disappearing. + /// + /// Will withdraw when not empty, or when focused while enabled. + bool get _labelShouldWithdraw => + !isEmpty || (isFocused && decoration.enabled); + + @override + State createState() => _InputDecoratorState(); + + /// The RenderBox that defines this decorator's "container". That's the + /// area which is filled if [RichInputDecoration.filled] is true. It's the area + /// adjacent to [RichInputDecoration.icon] and above the widgets that contain + /// [RichInputDecoration.helperText], [RichInputDecoration.errorText], and + /// [RichInputDecoration.counterText]. + /// + /// [TextField] renders ink splashes within the container. + static RenderBox? containerOf(BuildContext context) { + final _RenderDecoration? result = + context.findAncestorRenderObjectOfType<_RenderDecoration>(); + return result?.container; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add( + DiagnosticsProperty('decoration', decoration)); + properties.add(DiagnosticsProperty('baseStyle', baseStyle, + defaultValue: null)); + properties.add(DiagnosticsProperty('isFocused', isFocused)); + properties.add( + DiagnosticsProperty('expands', expands, defaultValue: false)); + properties.add(DiagnosticsProperty('isEmpty', isEmpty)); + } +} + +class _InputDecoratorState extends State + with TickerProviderStateMixin { + late final AnimationController _floatingLabelController; + late final Animation _floatingLabelAnimation; + late final AnimationController _shakingLabelController; + final _InputBorderGap _borderGap = _InputBorderGap(); + static const OrdinalSortKey _kPrefixSemanticsSortOrder = OrdinalSortKey(0); + static const OrdinalSortKey _kInputSemanticsSortOrder = OrdinalSortKey(1); + static const OrdinalSortKey _kSuffixSemanticsSortOrder = OrdinalSortKey(2); + static const SemanticsTag _kPrefixSemanticsTag = + SemanticsTag('_InputDecoratorState.prefix'); + static const SemanticsTag _kSuffixSemanticsTag = + SemanticsTag('_InputDecoratorState.suffix'); + + @override + void initState() { + super.initState(); + + final bool labelIsInitiallyFloating = + widget.decoration.floatingLabelBehavior == + FloatingLabelBehavior.always || + (widget.decoration.floatingLabelBehavior != + FloatingLabelBehavior.never && + widget._labelShouldWithdraw); + + _floatingLabelController = AnimationController( + duration: _kTransitionDuration, + vsync: this, + value: labelIsInitiallyFloating ? 1.0 : 0.0, + ); + _floatingLabelController.addListener(_handleChange); + _floatingLabelAnimation = CurvedAnimation( + parent: _floatingLabelController, + curve: _kTransitionCurve, + reverseCurve: _kTransitionCurve.flipped, + ); + + _shakingLabelController = AnimationController( + duration: _kTransitionDuration, + vsync: this, + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _effectiveDecoration = null; + } + + @override + void dispose() { + _floatingLabelController.dispose(); + _shakingLabelController.dispose(); + _borderGap.dispose(); + super.dispose(); + } + + void _handleChange() { + setState(() { + // The _floatingLabelController's value has changed. + }); + } + + RichInputDecoration? _effectiveDecoration; + RichInputDecoration get decoration => _effectiveDecoration ??= + widget.decoration.applyDefaults(RTFConverter.convertMaterial2InputDecorationTheme( + Theme.of(context).inputDecorationTheme)); + + TextAlign? get textAlign => widget.textAlign; + bool get isFocused => widget.isFocused; + bool get _hasError => + decoration.errorText != null || decoration.error != null; + bool get isHovering => widget.isHovering && decoration.enabled; + bool get isEmpty => widget.isEmpty; + bool get _floatingLabelEnabled { + return decoration.floatingLabelBehavior != FloatingLabelBehavior.never; + } + + @override + void didUpdateWidget(RichInputDecorator old) { + super.didUpdateWidget(old); + if (widget.decoration != old.decoration) { + _effectiveDecoration = null; + } + + final bool floatBehaviorChanged = widget.decoration.floatingLabelBehavior != + old.decoration.floatingLabelBehavior; + + if (widget._labelShouldWithdraw != old._labelShouldWithdraw || + floatBehaviorChanged) { + if (_floatingLabelEnabled && + (widget._labelShouldWithdraw || + widget.decoration.floatingLabelBehavior == + FloatingLabelBehavior.always)) { + _floatingLabelController.forward(); + } else { + _floatingLabelController.reverse(); + } + } + + final String? errorText = decoration.errorText; + final String? oldErrorText = old.decoration.errorText; + + if (_floatingLabelController.isCompleted && + errorText != null && + errorText != oldErrorText) { + _shakingLabelController + ..value = 0.0 + ..forward(); + } + } + + Color _getDefaultM2BorderColor(ThemeData themeData) { + if (!decoration.enabled && !isFocused) { + return ((decoration.filled ?? false) && + !(decoration.border?.isOutline ?? false)) + ? Colors.transparent + : themeData.disabledColor; + } + if (_hasError) { + return themeData.colorScheme.error; + } + if (isFocused) { + return themeData.colorScheme.primary; + } + if (decoration.filled!) { + return themeData.hintColor; + } + final Color enabledColor = + themeData.colorScheme.onSurface.withOpacity(0.38); + if (isHovering) { + final Color hoverColor = decoration.hoverColor ?? + themeData.inputDecorationTheme.hoverColor ?? + themeData.hoverColor; + return Color.alphaBlend(hoverColor.withOpacity(0.12), enabledColor); + } + return enabledColor; + } + + Color _getFillColor(ThemeData themeData, InputDecorationTheme defaults) { + if (decoration.filled != true) { + // filled == null same as filled == false + return Colors.transparent; + } + if (decoration.fillColor != null) { + return MaterialStateProperty.resolveAs( + decoration.fillColor!, materialState); + } + return MaterialStateProperty.resolveAs(defaults.fillColor!, materialState); + } + + Color _getHoverColor(ThemeData themeData) { + if (decoration.filled == null || + !decoration.filled! || + isFocused || + !decoration.enabled) { + return Colors.transparent; + } + return decoration.hoverColor ?? + themeData.inputDecorationTheme.hoverColor ?? + themeData.hoverColor; + } + + Color _getIconColor(ThemeData themeData, InputDecorationTheme defaults) { + return MaterialStateProperty.resolveAs( + decoration.iconColor, materialState) ?? + MaterialStateProperty.resolveAs( + themeData.inputDecorationTheme.iconColor, materialState) ?? + MaterialStateProperty.resolveAs(defaults.iconColor!, materialState); + } + + Color _getPrefixIconColor( + ThemeData themeData, InputDecorationTheme defaults) { + return MaterialStateProperty.resolveAs( + decoration.prefixIconColor, materialState) ?? + MaterialStateProperty.resolveAs( + themeData.inputDecorationTheme.prefixIconColor, materialState) ?? + MaterialStateProperty.resolveAs( + defaults.prefixIconColor!, materialState); + } + + Color _getSuffixIconColor( + ThemeData themeData, InputDecorationTheme defaults) { + return MaterialStateProperty.resolveAs( + decoration.suffixIconColor, materialState) ?? + MaterialStateProperty.resolveAs( + themeData.inputDecorationTheme.suffixIconColor, materialState) ?? + MaterialStateProperty.resolveAs( + defaults.suffixIconColor!, materialState); + } + + // True if the label will be shown and the hint will not. + // If we're not focused, there's no value, labelTextSpan was provided, and + // floatingLabelBehavior isn't set to always, then the label appears where the + // hint would. + bool get _hasInlineLabel { + return !widget._labelShouldWithdraw && + (decoration.labelTextSpan != null || decoration.label != null) && + decoration.floatingLabelBehavior != FloatingLabelBehavior.always; + } + + // If the label is a floating placeholder, it's always shown. + bool get _shouldShowLabel => _hasInlineLabel || _floatingLabelEnabled; + + // The base style for the inline label when they're displayed "inline", + // i.e. when they appear in place of the empty text field. + TextStyle _getInlineLabelStyle( + ThemeData themeData, InputDecorationTheme defaults) { + final TextStyle defaultStyle = + MaterialStateProperty.resolveAs(defaults.labelStyle!, materialState); + + final TextStyle? style = + MaterialStateProperty.resolveAs(decoration.labelStyle, materialState) ?? + MaterialStateProperty.resolveAs( + themeData.inputDecorationTheme.labelStyle, materialState); + + return themeData.textTheme.titleMedium! + .merge(widget.baseStyle) + .merge(defaultStyle) + .merge(style) + .copyWith(height: 1); + } + + // The base style for the inline hint when they're displayed "inline", + // i.e. when they appear in place of the empty text field. + TextStyle _getInlineHintStyle( + ThemeData themeData, InputDecorationTheme defaults) { + final TextStyle defaultStyle = + MaterialStateProperty.resolveAs(defaults.hintStyle!, materialState); + + final TextStyle? style = + MaterialStateProperty.resolveAs(decoration.hintStyle, materialState) ?? + MaterialStateProperty.resolveAs( + themeData.inputDecorationTheme.hintStyle, materialState); + + return themeData.textTheme.titleMedium! + .merge(widget.baseStyle) + .merge(defaultStyle) + .merge(style); + } + + TextStyle _getFloatingLabelStyle( + ThemeData themeData, InputDecorationTheme defaults) { + TextStyle defaultTextStyle = MaterialStateProperty.resolveAs( + defaults.floatingLabelStyle!, materialState); + if (_hasError && decoration.errorStyle?.color != null) { + defaultTextStyle = + defaultTextStyle.copyWith(color: decoration.errorStyle?.color); + } + defaultTextStyle = defaultTextStyle + .merge(decoration.floatingLabelStyle ?? decoration.labelStyle); + + final TextStyle? style = MaterialStateProperty.resolveAs( + decoration.floatingLabelStyle, materialState) ?? + MaterialStateProperty.resolveAs( + themeData.inputDecorationTheme.floatingLabelStyle, materialState); + + return themeData.textTheme.titleMedium! + .merge(widget.baseStyle) + .copyWith(height: 1) + .merge(defaultTextStyle) + .merge(style); + } + + TextStyle _getHelperStyle( + ThemeData themeData, InputDecorationTheme defaults) { + return MaterialStateProperty.resolveAs(defaults.helperStyle!, materialState) + .merge(MaterialStateProperty.resolveAs( + decoration.helperStyle, materialState)); + } + + TextStyle _getErrorStyle(ThemeData themeData, InputDecorationTheme defaults) { + return MaterialStateProperty.resolveAs(defaults.errorStyle!, materialState) + .merge(decoration.errorStyle); + } + + Set get materialState { + return { + if (!decoration.enabled) MaterialState.disabled, + if (isFocused) MaterialState.focused, + if (isHovering) MaterialState.hovered, + if (_hasError) MaterialState.error, + }; + } + + InputBorder _getDefaultBorder( + ThemeData themeData, InputDecorationTheme defaults) { + final InputBorder border = + MaterialStateProperty.resolveAs(decoration.border, materialState) ?? + const UnderlineInputBorder(); + + if (decoration.border is MaterialStateProperty) { + return border; + } + + if (border.borderSide == BorderSide.none) { + return border; + } + + if (themeData.useMaterial3) { + if (decoration.filled!) { + return border.copyWith( + borderSide: MaterialStateProperty.resolveAs( + defaults.activeIndicatorBorder, materialState), + ); + } else { + return border.copyWith( + borderSide: MaterialStateProperty.resolveAs( + defaults.outlineBorder, materialState), + ); + } + } else { + return border.copyWith( + borderSide: BorderSide( + color: _getDefaultM2BorderColor(themeData), + width: ((decoration.isCollapsed ?? + themeData.inputDecorationTheme.isCollapsed) || + decoration.border == InputBorder.none || + !decoration.enabled) + ? 0.0 + : isFocused + ? 2.0 + : 1.0, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + final InputDecorationTheme defaults = Theme.of(context).useMaterial3 + ? _InputDecoratorDefaultsM3(context) + : _InputDecoratorDefaultsM2(context); + + final TextStyle labelStyle = _getInlineLabelStyle(themeData, defaults); + final TextBaseline textBaseline = labelStyle.textBaseline!; + + final TextStyle hintStyle = _getInlineHintStyle(themeData, defaults); + final TextSpan? hintText = decoration.hintTextSpan; + final Widget? hint = hintText == null + ? null + : AnimatedOpacity( + opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0, + duration: + decoration.hintFadeDuration ?? _kHintFadeTransitionDuration, + curve: _kTransitionCurve, + alwaysIncludeSemantics: true, + child: RichText( + text: TextSpan( + style: hintStyle, + children: decoration.hintTextSpan != null + ? [decoration.hintTextSpan!] + : null, + ), + maxLines: decoration.hintMaxLines, + overflow: TextOverflow.ellipsis, + textAlign: textAlign ?? TextAlign.start, + ), + ); + + InputBorder? border; + if (!decoration.enabled) { + border = _hasError ? decoration.errorBorder : decoration.disabledBorder; + } else if (isFocused) { + border = + _hasError ? decoration.focusedErrorBorder : decoration.focusedBorder; + } else { + border = _hasError ? decoration.errorBorder : decoration.enabledBorder; + } + border ??= _getDefaultBorder(themeData, defaults); + + final Widget container = _BorderContainer( + border: border, + gap: _borderGap, + gapAnimation: _floatingLabelAnimation, + fillColor: _getFillColor(themeData, defaults), + hoverColor: _getHoverColor(themeData), + isHovering: isHovering, + ); + + final Widget? label = + decoration.labelTextSpan == null && decoration.label == null + ? null + : _Shaker( + animation: _shakingLabelController.view, + child: AnimatedOpacity( + duration: _kTransitionDuration, + curve: _kTransitionCurve, + opacity: _shouldShowLabel ? 1.0 : 0.0, + child: AnimatedDefaultTextStyle( + duration: _kTransitionDuration, + curve: _kTransitionCurve, + style: widget._labelShouldWithdraw + ? _getFloatingLabelStyle(themeData, defaults) + : labelStyle, + child: decoration.label ?? + RichText( + text: TextSpan( + style: labelStyle, + children: decoration.labelTextSpan != null + ? [decoration.labelTextSpan!] + : null, + ), + maxLines: decoration.hintMaxLines, + overflow: TextOverflow.ellipsis, + textAlign: textAlign ?? TextAlign.start, + ), + ), + ), + ); + + final bool hasPrefix = + decoration.prefix != null || decoration.prefixText != null; + final bool hasSuffix = + decoration.suffix != null || decoration.suffixText != null; + + Widget? input = widget.child; + // If at least two out of the three are visible, it needs semantics sort + // order. + final bool needsSemanticsSortOrder = widget._labelShouldWithdraw && + (input != null ? (hasPrefix || hasSuffix) : (hasPrefix && hasSuffix)); + + final Widget? prefix = hasPrefix + ? _AffixText( + labelIsFloating: widget._labelShouldWithdraw, + text: decoration.prefixText, + style: MaterialStateProperty.resolveAs( + decoration.prefixStyle, materialState) ?? + hintStyle, + semanticsSortKey: + needsSemanticsSortOrder ? _kPrefixSemanticsSortOrder : null, + semanticsTag: _kPrefixSemanticsTag, + child: decoration.prefix, + ) + : null; + + final Widget? suffix = hasSuffix + ? _AffixText( + labelIsFloating: widget._labelShouldWithdraw, + text: decoration.suffixText, + style: MaterialStateProperty.resolveAs( + decoration.suffixStyle, materialState) ?? + hintStyle, + semanticsSortKey: + needsSemanticsSortOrder ? _kSuffixSemanticsSortOrder : null, + semanticsTag: _kSuffixSemanticsTag, + child: decoration.suffix, + ) + : null; + + if (input != null && needsSemanticsSortOrder) { + input = Semantics( + sortKey: _kInputSemanticsSortOrder, + child: input, + ); + } + + final bool decorationIsDense = decoration.isDense ?? false; + final double iconSize = decorationIsDense ? 18.0 : 24.0; + + final Widget? icon = decoration.icon == null + ? null + : MouseRegion( + cursor: SystemMouseCursors.basic, + child: Padding( + padding: const EdgeInsetsDirectional.only(end: 16.0), + child: IconTheme.merge( + data: IconThemeData( + color: _getIconColor(themeData, defaults), + size: iconSize, + ), + child: decoration.icon!, + ), + ), + ); + + final Widget? prefixIcon = decoration.prefixIcon == null + ? null + : Center( + widthFactor: 1.0, + heightFactor: 1.0, + child: MouseRegion( + cursor: SystemMouseCursors.basic, + child: ConstrainedBox( + constraints: decoration.prefixIconConstraints ?? + themeData.visualDensity.effectiveConstraints( + const BoxConstraints( + minWidth: kMinInteractiveDimension, + minHeight: kMinInteractiveDimension, + ), + ), + child: IconTheme.merge( + data: IconThemeData( + color: _getPrefixIconColor(themeData, defaults), + size: iconSize, + ), + child: IconButtonTheme( + data: IconButtonThemeData( + style: IconButton.styleFrom( + foregroundColor: + _getPrefixIconColor(themeData, defaults), + iconSize: iconSize, + ), + ), + child: Semantics( + child: decoration.prefixIcon, + ), + ), + ), + ), + ), + ); + + final Widget? suffixIcon = decoration.suffixIcon == null + ? null + : Center( + widthFactor: 1.0, + heightFactor: 1.0, + child: MouseRegion( + cursor: SystemMouseCursors.basic, + child: ConstrainedBox( + constraints: decoration.suffixIconConstraints ?? + themeData.visualDensity.effectiveConstraints( + const BoxConstraints( + minWidth: kMinInteractiveDimension, + minHeight: kMinInteractiveDimension, + ), + ), + child: IconTheme.merge( + data: IconThemeData( + color: _getSuffixIconColor(themeData, defaults), + size: iconSize, + ), + child: IconButtonTheme( + data: IconButtonThemeData( + style: IconButton.styleFrom( + foregroundColor: + _getSuffixIconColor(themeData, defaults), + iconSize: iconSize, + ), + ), + child: Semantics( + child: decoration.suffixIcon, + ), + ), + ), + ), + ), + ); + + final Widget helperError = _HelperError( + textAlign: textAlign, + helperText: decoration.helperText, + helperStyle: _getHelperStyle(themeData, defaults), + helperMaxLines: decoration.helperMaxLines, + error: decoration.error, + errorText: decoration.errorText, + errorStyle: _getErrorStyle(themeData, defaults), + errorMaxLines: decoration.errorMaxLines, + ); + + Widget? counter; + if (decoration.counter != null) { + counter = decoration.counter; + } else if (decoration.counterText != null && decoration.counterText != '') { + counter = Semantics( + container: true, + liveRegion: isFocused, + child: Text( + decoration.counterText!, + style: _getHelperStyle(themeData, defaults).merge( + MaterialStateProperty.resolveAs( + decoration.counterStyle, materialState)), + overflow: TextOverflow.ellipsis, + semanticsLabel: decoration.semanticCounterText, + ), + ); + } + + // The _Decoration widget and _RenderDecoration assume that contentPadding + // has been resolved to EdgeInsets. + final TextDirection textDirection = Directionality.of(context); + final EdgeInsets? decorationContentPadding = + decoration.contentPadding?.resolve(textDirection); + + final EdgeInsets contentPadding; + final double floatingLabelHeight; + if (decoration.isCollapsed ?? themeData.inputDecorationTheme.isCollapsed) { + floatingLabelHeight = 0.0; + contentPadding = decorationContentPadding ?? EdgeInsets.zero; + } else if (!border.isOutline) { + // 4.0: the vertical gap between the inline elements and the floating label. + floatingLabelHeight = (4.0 + 0.75 * labelStyle.fontSize!) * + // ignore: deprecated_member_use + MediaQuery.textScalerOf(context).textScaleFactor; + if (decoration.filled ?? false) { + contentPadding = decorationContentPadding ?? + (decorationIsDense + ? const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0) + : const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0)); + } else { + // Not left or right padding for underline borders that aren't filled + // is a small concession to backwards compatibility. This eliminates + // the most noticeable layout change introduced by #13734. + contentPadding = decorationContentPadding ?? + (decorationIsDense + ? const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 8.0) + : const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 12.0)); + } + } else { + floatingLabelHeight = 0.0; + contentPadding = decorationContentPadding ?? + (decorationIsDense + ? const EdgeInsets.fromLTRB(12.0, 20.0, 12.0, 12.0) + : const EdgeInsets.fromLTRB(12.0, 24.0, 12.0, 16.0)); + } + + final _Decorator decorator = _Decorator( + decoration: _Decoration( + contentPadding: contentPadding, + isCollapsed: decoration.isCollapsed ?? + themeData.inputDecorationTheme.isCollapsed, + floatingLabelHeight: floatingLabelHeight, + floatingLabelAlignment: decoration.floatingLabelAlignment!, + floatingLabelProgress: _floatingLabelAnimation.value, + border: border, + borderGap: _borderGap, + alignLabelWithHint: decoration.alignLabelWithHint ?? false, + isDense: decoration.isDense, + visualDensity: themeData.visualDensity, + icon: icon, + input: input, + label: label, + hint: hint, + prefix: prefix, + suffix: suffix, + prefixIcon: prefixIcon, + suffixIcon: suffixIcon, + helperError: helperError, + counter: counter, + container: container), + textDirection: textDirection, + textBaseline: textBaseline, + textAlignVertical: widget.textAlignVertical, + isFocused: isFocused, + expands: widget.expands, + ); + + final BoxConstraints? constraints = + decoration.constraints ?? themeData.inputDecorationTheme.constraints; + if (constraints != null) { + return ConstrainedBox( + constraints: constraints, + child: decorator, + ); + } + return decorator; + } +} + +/// The border, labels, icons, and styles used to decorate a Material +/// Design text field. +/// +/// The [TextField] and [RichInputDecorator] classes use [RichInputDecoration] objects +/// to describe their decoration. (In fact, this class is merely the +/// configuration of an [RichInputDecorator], which does all the heavy lifting.) +/// +/// {@tool dartpad} +/// This sample shows how to style a `TextField` using an `RichInputDecorator`. The +/// TextField displays a "send message" icon to the left of the input area, +/// which is surrounded by a border an all sides. It displays the `hintText` +/// inside the input area to help the user understand what input is required. It +/// displays the `helperText` and `counterText` below the input area. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/input_decoration.png) +/// +/// ** See code in examples/api/lib/material/input_decorator/input_decoration.0.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This sample shows how to style a "collapsed" `TextField` using an +/// `RichInputDecorator`. The collapsed `TextField` surrounds the hint text and +/// input area with a border, but does not add padding around them. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/input_decoration_collapsed.png) +/// +/// ** See code in examples/api/lib/material/input_decorator/input_decoration.1.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This sample shows how to create a `TextField` with hint text, a red border +/// on all sides, and an error message. To display a red border and error +/// message, provide `errorText` to the [RichInputDecoration] constructor. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/input_decoration_error.png) +/// +/// ** See code in examples/api/lib/material/input_decorator/input_decoration.2.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This sample shows how to style a `TextField` with a round border and +/// additional text before and after the input area. It displays "Prefix" before +/// the input area, and "Suffix" after the input area. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/input_decoration_prefix_suffix.png) +/// +/// ** See code in examples/api/lib/material/input_decorator/input_decoration.3.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This sample shows how to style a `TextField` with a prefixIcon that changes color +/// based on the `MaterialState`. The color defaults to gray, be blue while focused +/// and red if in an error state. +/// +/// ** See code in examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This sample shows how to style a `TextField` with a prefixIcon that changes color +/// based on the `MaterialState` through the use of `ThemeData`. The color defaults +/// to gray, be blue while focused and red if in an error state. +/// +/// ** See code in examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * [TextField], which is a text input widget that uses an +/// [RichInputDecoration]. +/// * [RichInputDecorator], which is a widget that draws an [RichInputDecoration] +/// around an input child widget. +/// * [Decoration] and [DecoratedBox], for drawing borders and backgrounds +/// around a child widget. +@immutable +class RichInputDecoration { + /// Creates a bundle of the border, labels, icons, and styles used to + /// decorate a Material Design text field. + /// + /// Unless specified by [ThemeData.inputDecorationTheme], [RichInputDecorator] + /// defaults [isDense] to false and [filled] to false. The default border is + /// an instance of [UnderlineInputBorder]. If [border] is [InputBorder.none] + /// then no border is drawn. + /// + /// Only one of [prefix] and [prefixText] can be specified. + /// + /// Similarly, only one of [suffix] and [suffixText] can be specified. + const RichInputDecoration({ + this.icon, + this.iconColor, + this.label, + this.labelTextSpan, + this.labelStyle, + this.floatingLabelStyle, + this.helperText, + this.helperStyle, + this.helperMaxLines, + this.hintTextSpan, + this.hintStyle, + this.hintTextDirection, + this.hintMaxLines, + this.hintFadeDuration, + this.error, + this.errorText, + this.errorStyle, + this.errorMaxLines, + this.floatingLabelBehavior, + this.floatingLabelAlignment, + this.isCollapsed, + this.isDense, + this.contentPadding, + this.prefixIcon, + this.prefixIconConstraints, + this.prefix, + this.prefixText, + this.prefixStyle, + this.prefixIconColor, + this.suffixIcon, + this.suffix, + this.suffixText, + this.suffixStyle, + this.suffixIconColor, + this.suffixIconConstraints, + this.counter, + this.counterText, + this.counterStyle, + this.filled, + this.fillColor, + this.focusColor, + this.hoverColor, + this.errorBorder, + this.focusedBorder, + this.focusedErrorBorder, + this.disabledBorder, + this.enabledBorder, + this.border, + this.enabled = true, + this.semanticCounterText, + this.alignLabelWithHint, + this.constraints, + }) : assert(!(label != null && labelTextSpan != null), + 'Declaring both label and labelTextSpan is not supported.'), + assert(!(prefix != null && prefixText != null), + 'Declaring both prefix and prefixText is not supported.'), + assert(!(suffix != null && suffixText != null), + 'Declaring both suffix and suffixText is not supported.'), + assert(!(error != null && errorText != null), + 'Declaring both error and errorText is not supported.'); + + /// Defines an [RichInputDecorator] that is the same size as the input field. + /// + /// This type of input decoration does not include a border by default. + /// + /// Sets the [isCollapsed] property to true. + const RichInputDecoration.collapsed({ + required this.hintTextSpan, + this.floatingLabelBehavior, + this.floatingLabelAlignment, + this.hintStyle, + this.hintTextDirection, + this.filled = false, + this.fillColor, + this.focusColor, + this.hoverColor, + this.border = InputBorder.none, + this.enabled = true, + }) : icon = null, + iconColor = null, + label = null, + labelTextSpan = null, + labelStyle = null, + floatingLabelStyle = null, + helperText = null, + helperStyle = null, + helperMaxLines = null, + hintMaxLines = null, + hintFadeDuration = null, + error = null, + errorText = null, + errorStyle = null, + errorMaxLines = null, + isDense = false, + contentPadding = EdgeInsets.zero, + isCollapsed = true, + prefixIcon = null, + prefix = null, + prefixText = null, + prefixStyle = null, + prefixIconColor = null, + prefixIconConstraints = null, + suffix = null, + suffixIcon = null, + suffixText = null, + suffixStyle = null, + suffixIconColor = null, + suffixIconConstraints = null, + counter = null, + counterText = null, + counterStyle = null, + errorBorder = null, + focusedBorder = null, + focusedErrorBorder = null, + disabledBorder = null, + enabledBorder = null, + semanticCounterText = null, + alignLabelWithHint = false, + constraints = null; + + /// An icon to show before the input field and outside of the decoration's + /// container. + /// + /// The size and color of the icon is configured automatically using an + /// [IconTheme] and therefore does not need to be explicitly given in the + /// icon widget. + /// + /// The trailing edge of the icon is padded by 16dps. + /// + /// The decoration's container is the area which is filled if [filled] is + /// true and bordered per the [border]. It's the area adjacent to + /// [icon] and above the widgets that contain [helperText], + /// [errorText], and [counterText]. + /// + /// See [Icon], [ImageIcon]. + final Widget? icon; + + /// The color of the [icon]. + /// + /// If [iconColor] is a [MaterialStateColor], then the effective + /// color can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + final Color? iconColor; + + /// Optional widget that describes the input field. + /// + /// {@template flutter.material.inputDecoration.label} + /// When the input field is empty and unfocused, the label is displayed on + /// top of the input field (i.e., at the same location on the screen where + /// text may be entered in the input field). When the input field receives + /// focus (or if the field is non-empty), depending on [floatingLabelAlignment], + /// the label moves above, either vertically adjacent to, or to the center of + /// the input field. + /// {@endtemplate} + /// + /// This can be used, for example, to add multiple [TextStyle]'s to a label that would + /// otherwise be specified using [labelTextSpan], which only takes one [TextStyle]. + /// + /// {@tool dartpad} + /// This example shows a `TextField` with a [Text.rich] widget as the [label]. + /// The widget contains multiple [Text] widgets with different [TextStyle]'s. + /// + /// ** See code in examples/api/lib/material/input_decorator/input_decoration.label.0.dart ** + /// {@end-tool} + /// + /// Only one of [label] and [labelTextSpan] can be specified. + final Widget? label; + + /// Optional text that describes the input field. + /// + /// {@macro flutter.material.inputDecoration.label} + /// + /// If a more elaborate label is required, consider using [label] instead. + /// Only one of [label] and [labelTextSpan] can be specified. + final TextSpan? labelTextSpan; + + /// {@template flutter.material.inputDecoration.labelStyle} + /// The style to use for [RichInputDecoration.labelTextSpan] when the label is on top + /// of the input field. + /// + /// If [labelStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// When the [RichInputDecoration.labelTextSpan] is above (i.e., vertically adjacent to) + /// the input field, the text uses the [floatingLabelStyle] instead. + /// + /// If null, defaults to a value derived from the base [TextStyle] for the + /// input field and the current [Theme]. + /// + /// Specifying this style will override the default behavior + /// of [RichInputDecoration] that changes the color of the label to the + /// [RichInputDecoration.errorStyle] color or [ColorScheme.error]. + /// + /// {@tool dartpad} + /// It's possible to override the label style for just the error state, or + /// just the default state, or both. + /// + /// In this example the [labelStyle] is specified with a [MaterialStateProperty] + /// which resolves to a text style whose color depends on the decorator's + /// error state. + /// + /// ** See code in examples/api/lib/material/input_decorator/input_decoration.label_style_error.0.dart ** + /// {@end-tool} + /// {@endtemplate} + final TextStyle? labelStyle; + + /// {@template flutter.material.inputDecoration.floatingLabelStyle} + /// The style to use for [RichInputDecoration.labelTextSpan] when the label is + /// above (i.e., vertically adjacent to) the input field. + /// + /// When the [RichInputDecoration.labelTextSpan] is on top of the input field, the + /// text uses the [labelStyle] instead. + /// + /// If [floatingLabelStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// If null, defaults to [labelStyle]. + /// + /// Specifying this style will override the default behavior + /// of [RichInputDecoration] that changes the color of the label to the + /// [RichInputDecoration.errorStyle] color or [ColorScheme.error]. + /// + /// {@tool dartpad} + /// It's possible to override the label style for just the error state, or + /// just the default state, or both. + /// + /// In this example the [floatingLabelStyle] is specified with a + /// [MaterialStateProperty] which resolves to a text style whose color depends + /// on the decorator's error state. + /// + /// ** See code in examples/api/lib/material/input_decorator/input_decoration.floating_label_style_error.0.dart ** + /// {@end-tool} + /// {@endtemplate} + final TextStyle? floatingLabelStyle; + + /// Text that provides context about the [RichInputDecorator.child]'s value, such + /// as how the value will be used. + /// + /// If non-null, the text is displayed below the [RichInputDecorator.child], in + /// the same location as [errorText]. If a non-null [errorText] value is + /// specified then the helper text is not shown. + final String? helperText; + + /// The style to use for the [helperText]. + /// + /// If [helperStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + final TextStyle? helperStyle; + + /// The maximum number of lines the [helperText] can occupy. + /// + /// Defaults to null, which means that the [helperText] will be limited + /// to a single line with [TextOverflow.ellipsis]. + /// + /// This value is passed along to the [Text.maxLines] attribute + /// of the [Text] widget used to display the helper. + /// + /// See also: + /// + /// * [errorMaxLines], the equivalent but for the [errorText]. + final int? helperMaxLines; + + /// Text that suggests what sort of input the field accepts. + /// + /// Displayed on top of the [RichInputDecorator.child] (i.e., at the same location + /// on the screen where text may be entered in the [RichInputDecorator.child]) + /// when the input [isEmpty] and either (a) [labelTextSpan] is null or (b) the + /// input has the focus. + final TextSpan? hintTextSpan; + + /// The style to use for the [hintText]. + /// + /// If [hintStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// Also used for the [labelTextSpan] when the [labelTextSpan] is displayed on + /// top of the input field (i.e., at the same location on the screen where + /// text may be entered in the [RichInputDecorator.child]). + /// + /// If null, defaults to a value derived from the base [TextStyle] for the + /// input field and the current [Theme]. + final TextStyle? hintStyle; + + /// The direction to use for the [hintText]. + /// + /// If null, defaults to a value derived from [Directionality] for the + /// input field and the current context. + final TextDirection? hintTextDirection; + + /// The maximum number of lines the [hintText] can occupy. + /// + /// Defaults to the value of [TextField.maxLines] attribute. + /// + /// This value is passed along to the [Text.maxLines] attribute + /// of the [Text] widget used to display the hint text. [TextOverflow.ellipsis] is + /// used to handle the overflow when it is limited to single line. + final int? hintMaxLines; + + /// The duration of the [hintText] fade in and fade out animations. + /// + /// If null, defaults to [InputDecorationTheme.hintFadeDuration]. + /// If [InputDecorationTheme.hintFadeDuration] is null defaults to 20ms. + final Duration? hintFadeDuration; + + /// Optional widget that appears below the [RichInputDecorator.child] and the border. + /// + /// If non-null, the border's color animates to red and the [helperText] is not shown. + /// + /// Only one of [error] and [errorText] can be specified. + final Widget? error; + + /// Text that appears below the [RichInputDecorator.child] and the border. + /// + /// If non-null, the border's color animates to red and the [helperText] is + /// not shown. + /// + /// In a [TextFormField], this is overridden by the value returned from + /// [TextFormField.validator], if that is not null. + /// + /// If a more elaborate error is required, consider using [error] instead. + /// + /// Only one of [error] and [errorText] can be specified. + final String? errorText; + + /// {@template flutter.material.inputDecoration.errorStyle} + /// The style to use for the [RichInputDecoration.errorText]. + /// + /// If null, defaults of a value derived from the base [TextStyle] for the + /// input field and the current [Theme]. + /// + /// By default the color of style will be used by the label of + /// [RichInputDecoration] if [RichInputDecoration.errorText] is not null. See + /// [RichInputDecoration.labelStyle] or [RichInputDecoration.floatingLabelStyle] for + /// an example of how to replicate this behavior when specifying those + /// styles. + /// {@endtemplate} + final TextStyle? errorStyle; + + /// The maximum number of lines the [errorText] can occupy. + /// + /// Defaults to null, which means that the [errorText] will be limited + /// to a single line with [TextOverflow.ellipsis]. + /// + /// This value is passed along to the [Text.maxLines] attribute + /// of the [Text] widget used to display the error. + /// + /// See also: + /// + /// * [helperMaxLines], the equivalent but for the [helperText]. + final int? errorMaxLines; + + /// {@template flutter.material.inputDecoration.floatingLabelBehavior} + /// Defines **how** the floating label should behave. + /// + /// When [FloatingLabelBehavior.auto] the label will float to the top only when + /// the field is focused or has some text content, otherwise it will appear + /// in the field in place of the content. + /// + /// When [FloatingLabelBehavior.always] the label will always float at the top + /// of the field above the content. + /// + /// When [FloatingLabelBehavior.never] the label will always appear in an empty + /// field in place of the content. + /// {@endtemplate} + /// + /// If null, [InputDecorationTheme.floatingLabelBehavior] will be used. + /// + /// See also: + /// + /// * [floatingLabelAlignment] which defines **where** the floating label + /// should be displayed. + final FloatingLabelBehavior? floatingLabelBehavior; + + /// {@template flutter.material.inputDecoration.floatingLabelAlignment} + /// Defines **where** the floating label should be displayed. + /// + /// [FloatingLabelAlignment.start] aligns the floating label to the leftmost + /// (when [TextDirection.ltr]) or rightmost (when [TextDirection.rtl]), + /// possible position, which is vertically adjacent to the label, on top of + /// the field. + /// + /// [FloatingLabelAlignment.center] aligns the floating label to the center on + /// top of the field. + /// {@endtemplate} + /// + /// If null, [InputDecorationTheme.floatingLabelAlignment] will be used. + /// + /// See also: + /// + /// * [floatingLabelBehavior] which defines **how** the floating label should + /// behave. + final FloatingLabelAlignment? floatingLabelAlignment; + + /// Whether the [RichInputDecorator.child] is part of a dense form (i.e., uses less vertical + /// space). + /// + /// Defaults to false. + final bool? isDense; + + /// The padding for the input decoration's container. + /// + /// {@macro flutter.material.input_decorator.container_description} + /// + /// By default the [contentPadding] reflects [isDense] and the type of the + /// [border]. + /// + /// If [isCollapsed] is true then [contentPadding] is [EdgeInsets.zero]. + /// + /// If `isOutline` property of [border] is false and if [filled] is true then + /// [contentPadding] is `EdgeInsets.fromLTRB(12, 8, 12, 8)` when [isDense] + /// is true and `EdgeInsets.fromLTRB(12, 12, 12, 12)` when [isDense] is false. + /// If `isOutline` property of [border] is false and if [filled] is false then + /// [contentPadding] is `EdgeInsets.fromLTRB(0, 8, 0, 8)` when [isDense] is + /// true and `EdgeInsets.fromLTRB(0, 12, 0, 12)` when [isDense] is false. + /// + /// If `isOutline` property of [border] is true then [contentPadding] is + /// `EdgeInsets.fromLTRB(12, 20, 12, 12)` when [isDense] is true + /// and `EdgeInsets.fromLTRB(12, 24, 12, 16)` when [isDense] is false. + final EdgeInsetsGeometry? contentPadding; + + /// Whether the decoration is the same size as the input field. + /// + /// A collapsed decoration cannot have [labelTextSpan], [errorText], an [icon]. + /// + /// To create a collapsed input decoration, use [RichInputDecoration.collapsed]. + final bool? isCollapsed; + + /// An icon that appears before the [prefix] or [prefixText] and before + /// the editable part of the text field, within the decoration's container. + /// + /// The size and color of the prefix icon is configured automatically using an + /// [IconTheme] and therefore does not need to be explicitly given in the + /// icon widget. + /// + /// The prefix icon is constrained with a minimum size of 48px by 48px, but + /// can be expanded beyond that. Anything larger than 24px will require + /// additional padding to ensure it matches the Material Design spec of 12px + /// padding between the left edge of the input and leading edge of the prefix + /// icon. The following snippet shows how to pad the leading edge of the + /// prefix icon: + /// + /// ```dart + /// prefixIcon: Padding( + /// padding: const EdgeInsetsDirectional.only(start: 12.0), + /// child: _myIcon, // _myIcon is a 48px-wide widget. + /// ) + /// ``` + /// + /// {@macro flutter.material.input_decorator.container_description} + /// + /// The prefix icon alignment can be changed using [Align] with a fixed `widthFactor` and + /// `heightFactor`. + /// + /// {@tool dartpad} + /// This example shows how the prefix icon alignment can be changed using [Align] with + /// a fixed `widthFactor` and `heightFactor`. + /// + /// ** See code in examples/api/lib/material/input_decorator/input_decoration.prefix_icon.0.dart ** + /// {@end-tool} + /// + /// See also: + /// + /// * [Icon] and [ImageIcon], which are typically used to show icons. + /// * [prefix] and [prefixText], which are other ways to show content + /// before the text field (but after the icon). + /// * [suffixIcon], which is the same but on the trailing edge. + /// * [Align] A widget that aligns its child within itself and optionally + /// sizes itself based on the child's size. + final Widget? prefixIcon; + + /// The constraints for the prefix icon. + /// + /// This can be used to modify the [BoxConstraints] surrounding [prefixIcon]. + /// + /// This property is particularly useful for getting the decoration's height + /// less than 48px. This can be achieved by setting [isDense] to true and + /// setting the constraints' minimum height and width to a value lower than + /// 48px. + /// + /// {@tool dartpad} + /// This example shows the differences between two `TextField` widgets when + /// [prefixIconConstraints] is set to the default value and when one is not. + /// + /// The [isDense] property must be set to true to be able to + /// set the constraints smaller than 48px. + /// + /// If null, [BoxConstraints] with a minimum width and height of 48px is + /// used. + /// + /// ** See code in examples/api/lib/material/input_decorator/input_decoration.prefix_icon_constraints.0.dart ** + /// {@end-tool} + final BoxConstraints? prefixIconConstraints; + + /// Optional widget to place on the line before the input. + /// + /// This can be used, for example, to add some padding to text that would + /// otherwise be specified using [prefixText], or to add a custom widget in + /// front of the input. The widget's baseline is lined up with the input + /// baseline. + /// + /// Only one of [prefix] and [prefixText] can be specified. + /// + /// The [prefix] appears after the [prefixIcon], if both are specified. + /// + /// See also: + /// + /// * [suffix], the equivalent but on the trailing edge. + final Widget? prefix; + + /// Optional text prefix to place on the line before the input. + /// + /// Uses the [prefixStyle]. Uses [hintStyle] if [prefixStyle] isn't specified. + /// The prefix text is not returned as part of the user's input. + /// + /// If a more elaborate prefix is required, consider using [prefix] instead. + /// Only one of [prefix] and [prefixText] can be specified. + /// + /// The [prefixText] appears after the [prefixIcon], if both are specified. + /// + /// See also: + /// + /// * [suffixText], the equivalent but on the trailing edge. + final String? prefixText; + + /// The style to use for the [prefixText]. + /// + /// If [prefixStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// If null, defaults to the [hintStyle]. + /// + /// See also: + /// + /// * [suffixStyle], the equivalent but on the trailing edge. + final TextStyle? prefixStyle; + + /// Optional color of the prefixIcon + /// + /// Defaults to [iconColor] + /// + /// If [prefixIconColor] is a [MaterialStateColor], then the effective + /// color can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + final Color? prefixIconColor; + + /// An icon that appears after the editable part of the text field and + /// after the [suffix] or [suffixText], within the decoration's container. + /// + /// The size and color of the suffix icon is configured automatically using an + /// [IconTheme] and therefore does not need to be explicitly given in the + /// icon widget. + /// + /// The suffix icon is constrained with a minimum size of 48px by 48px, but + /// can be expanded beyond that. Anything larger than 24px will require + /// additional padding to ensure it matches the Material Design spec of 12px + /// padding between the right edge of the input and trailing edge of the + /// prefix icon. The following snippet shows how to pad the trailing edge of + /// the suffix icon: + /// + /// ```dart + /// suffixIcon: Padding( + /// padding: const EdgeInsetsDirectional.only(end: 12.0), + /// child: _myIcon, // myIcon is a 48px-wide widget. + /// ) + /// ``` + /// + /// The decoration's container is the area which is filled if [filled] is + /// true and bordered per the [border]. It's the area adjacent to + /// [icon] and above the widgets that contain [helperText], + /// [errorText], and [counterText]. + /// + /// The suffix icon alignment can be changed using [Align] with a fixed `widthFactor` and + /// `heightFactor`. + /// + /// {@tool dartpad} + /// This example shows how the suffix icon alignment can be changed using [Align] with + /// a fixed `widthFactor` and `heightFactor`. + /// + /// ** See code in examples/api/lib/material/input_decorator/input_decoration.suffix_icon.0.dart ** + /// {@end-tool} + /// + /// See also: + /// + /// * [Icon] and [ImageIcon], which are typically used to show icons. + /// * [suffix] and [suffixText], which are other ways to show content + /// after the text field (but before the icon). + /// * [prefixIcon], which is the same but on the leading edge. + /// * [Align] A widget that aligns its child within itself and optionally + /// sizes itself based on the child's size. + final Widget? suffixIcon; + + /// Optional widget to place on the line after the input. + /// + /// This can be used, for example, to add some padding to the text that would + /// otherwise be specified using [suffixText], or to add a custom widget after + /// the input. The widget's baseline is lined up with the input baseline. + /// + /// Only one of [suffix] and [suffixText] can be specified. + /// + /// The [suffix] appears before the [suffixIcon], if both are specified. + /// + /// See also: + /// + /// * [prefix], the equivalent but on the leading edge. + final Widget? suffix; + + /// Optional text suffix to place on the line after the input. + /// + /// Uses the [suffixStyle]. Uses [hintStyle] if [suffixStyle] isn't specified. + /// The suffix text is not returned as part of the user's input. + /// + /// If a more elaborate suffix is required, consider using [suffix] instead. + /// Only one of [suffix] and [suffixText] can be specified. + /// + /// The [suffixText] appears before the [suffixIcon], if both are specified. + /// + /// See also: + /// + /// * [prefixText], the equivalent but on the leading edge. + final String? suffixText; + + /// The style to use for the [suffixText]. + /// + /// If [suffixStyle] is a [MaterialStateTextStyle], then the effective text + /// style can depend on the [MaterialState.focused] state, i.e. if the + /// [TextField] is focused or not. + /// + /// If null, defaults to the [hintStyle]. + /// + /// See also: + /// + /// * [prefixStyle], the equivalent but on the leading edge. + final TextStyle? suffixStyle; + + /// Optional color of the [suffixIcon]. + /// + /// Defaults to [iconColor] + /// + /// If [suffixIconColor] is a [MaterialStateColor], then the effective + /// color can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + final Color? suffixIconColor; + + /// The constraints for the suffix icon. + /// + /// This can be used to modify the [BoxConstraints] surrounding [suffixIcon]. + /// + /// This property is particularly useful for getting the decoration's height + /// less than 48px. This can be achieved by setting [isDense] to true and + /// setting the constraints' minimum height and width to a value lower than + /// 48px. + /// + /// If null, a [BoxConstraints] with a minimum width and height of 48px is + /// used. + /// + /// {@tool dartpad} + /// This example shows the differences between two `TextField` widgets when + /// [suffixIconConstraints] is set to the default value and when one is not. + /// + /// The [isDense] property must be set to true to be able to + /// set the constraints smaller than 48px. + /// + /// If null, [BoxConstraints] with a minimum width and height of 48px is + /// used. + /// + /// ** See code in examples/api/lib/material/input_decorator/input_decoration.suffix_icon_constraints.0.dart ** + /// {@end-tool} + final BoxConstraints? suffixIconConstraints; + + /// Optional text to place below the line as a character count. + /// + /// Rendered using [counterStyle]. Uses [helperStyle] if [counterStyle] is + /// null. + /// + /// The semantic label can be replaced by providing a [semanticCounterText]. + /// + /// If null or an empty string and [counter] isn't specified, then nothing + /// will appear in the counter's location. + final String? counterText; + + /// Optional custom counter widget to go in the place otherwise occupied by + /// [counterText]. If this property is non null, then [counterText] is + /// ignored. + final Widget? counter; + + /// The style to use for the [counterText]. + /// + /// If [counterStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// If null, defaults to the [helperStyle]. + final TextStyle? counterStyle; + + /// If true the decoration's container is filled with [fillColor]. + /// + /// When [RichInputDecorator.isHovering] is true, the [hoverColor] is also blended + /// into the final fill color. + /// + /// Typically this field set to true if [border] is an [UnderlineInputBorder]. + /// + /// {@template flutter.material.input_decorator.container_description} + /// The decoration's container is the area which is filled if [filled] is true + /// and bordered per the [border]. It's the area adjacent to [icon] and above + /// the widgets that contain [helperText], [errorText], and [counterText]. + /// {@endtemplate} + /// + /// This property is false by default. + final bool? filled; + + /// The base fill color of the decoration's container color. + /// + /// When [RichInputDecorator.isHovering] is true, the [hoverColor] is also blended + /// into the final fill color. + /// + /// By default the [fillColor] is based on the current + /// [InputDecorationTheme.fillColor]. + /// + /// {@macro flutter.material.input_decorator.container_description} + final Color? fillColor; + + /// The fill color of the decoration's container when it has the input focus. + /// + /// By default the [focusColor] is based on the current + /// [InputDecorationTheme.focusColor]. + /// + /// This [focusColor] is ignored by [TextField] and [TextFormField] because + /// they don't respond to focus changes by changing their decorator's + /// container color, they respond by changing their border to the + /// [focusedBorder], which you can change the color of. + /// + /// {@macro flutter.material.input_decorator.container_description} + final Color? focusColor; + + /// The color of the highlight for the decoration shown if the container + /// is being hovered over by a mouse. + /// + /// If [filled] is true, the [hoverColor] is blended with [fillColor] and + /// fills the decoration's container. + /// + /// If [filled] is false, and [RichInputDecorator.isFocused] is false, the color + /// is blended over the [enabledBorder]'s color. + /// + /// By default the [hoverColor] is based on the current [Theme]. + /// + /// {@macro flutter.material.input_decorator.container_description} + final Color? hoverColor; + + /// The border to display when the [RichInputDecorator] does not have the focus and + /// is showing an error. + /// + /// See also: + /// + /// * [RichInputDecorator.isFocused], which is true if the [RichInputDecorator]'s child + /// has the focus. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [focusedBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [RichInputDecoration.enabled] is false + /// and [RichInputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [RichInputDecoration.enabled] is true + /// and [RichInputDecoration.errorText] is null. + final InputBorder? errorBorder; + + /// The border to display when the [RichInputDecorator] has the focus and is not + /// showing an error. + /// + /// See also: + /// + /// * [RichInputDecorator.isFocused], which is true if the [RichInputDecorator]'s child + /// has the focus. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [RichInputDecorator.isFocused] is false + /// and [RichInputDecoration.errorText] is non-null. + /// * [focusedErrorBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [RichInputDecoration.enabled] is false + /// and [RichInputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [RichInputDecoration.enabled] is true + /// and [RichInputDecoration.errorText] is null. + final InputBorder? focusedBorder; + + /// The border to display when the [RichInputDecorator] has the focus and is + /// showing an error. + /// + /// See also: + /// + /// * [RichInputDecorator.isFocused], which is true if the [RichInputDecorator]'s child + /// has the focus. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [RichInputDecorator.isFocused] is false + /// and [RichInputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is null. + /// * [disabledBorder], displayed when [RichInputDecoration.enabled] is false + /// and [RichInputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [RichInputDecoration.enabled] is true + /// and [RichInputDecoration.errorText] is null. + final InputBorder? focusedErrorBorder; + + /// The border to display when the [RichInputDecorator] is disabled and is not + /// showing an error. + /// + /// See also: + /// + /// * [RichInputDecoration.enabled], which is false if the [RichInputDecorator] is disabled. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [RichInputDecorator.isFocused] is false + /// and [RichInputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is non-null. + /// * [enabledBorder], displayed when [RichInputDecoration.enabled] is true + /// and [RichInputDecoration.errorText] is null. + final InputBorder? disabledBorder; + + /// The border to display when the [RichInputDecorator] is enabled and is not + /// showing an error. + /// + /// See also: + /// + /// * [RichInputDecoration.enabled], which is false if the [RichInputDecorator] is disabled. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [RichInputDecorator.isFocused] is false + /// and [RichInputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [RichInputDecoration.enabled] is false + /// and [RichInputDecoration.errorText] is null. + final InputBorder? enabledBorder; + + /// The shape of the border to draw around the decoration's container. + /// + /// If [border] is a [MaterialStateUnderlineInputBorder] + /// or [MaterialStateOutlineInputBorder], then the effective border can depend on + /// the [MaterialState.focused] state, i.e. if the [TextField] is focused or not. + /// + /// If [border] derives from [InputBorder] the border's [InputBorder.borderSide], + /// i.e. the border's color and width, will be overridden to reflect the input + /// decorator's state. Only the border's shape is used. If custom [BorderSide] + /// values are desired for a given state, all four borders – [errorBorder], + /// [focusedBorder], [enabledBorder], [disabledBorder] – must be set. + /// + /// The decoration's container is the area which is filled if [filled] is + /// true and bordered per the [border]. It's the area adjacent to + /// [RichInputDecoration.icon] and above the widgets that contain + /// [RichInputDecoration.helperText], [RichInputDecoration.errorText], and + /// [RichInputDecoration.counterText]. + /// + /// The border's bounds, i.e. the value of `border.getOuterPath()`, define + /// the area to be filled. + /// + /// This property is only used when the appropriate one of [errorBorder], + /// [focusedBorder], [focusedErrorBorder], [disabledBorder], or [enabledBorder] + /// is not specified. This border's [InputBorder.borderSide] property is + /// configured by the RichInputDecorator, depending on the values of + /// [RichInputDecoration.errorText], [RichInputDecoration.enabled], + /// [RichInputDecorator.isFocused] and the current [Theme]. + /// + /// Typically one of [UnderlineInputBorder] or [OutlineInputBorder]. + /// If null, RichInputDecorator's default is `const UnderlineInputBorder()`. + /// + /// See also: + /// + /// * [InputBorder.none], which doesn't draw a border. + /// * [UnderlineInputBorder], which draws a horizontal line at the + /// bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + final InputBorder? border; + + /// If false [helperText],[errorText], and [counterText] are not displayed, + /// and the opacity of the remaining visual elements is reduced. + /// + /// This property is true by default. + final bool enabled; + + /// A semantic label for the [counterText]. + /// + /// Defaults to null. + /// + /// If provided, this replaces the semantic label of the [counterText]. + final String? semanticCounterText; + + /// Typically set to true when the [RichInputDecorator] contains a multiline + /// [TextField] ([TextField.maxLines] is null or > 1) to override the default + /// behavior of aligning the label with the center of the [TextField]. + /// + /// Defaults to false. + final bool? alignLabelWithHint; + + /// Defines minimum and maximum sizes for the [RichInputDecorator]. + /// + /// Typically the decorator will fill the horizontal space it is given. For + /// larger screens, it may be useful to have the maximum width clamped to + /// a given value so it doesn't fill the whole screen. This property + /// allows you to control how big the decorator will be in its available + /// space. + /// + /// If null, then the ambient [ThemeData.inputDecorationTheme]'s + /// [InputDecorationTheme.constraints] will be used. If that + /// is null then the decorator will fill the available width with + /// a default height based on text size. + final BoxConstraints? constraints; + + /// Creates a copy of this input decoration with the given fields replaced + /// by the new values. + RichInputDecoration copyWith({ + Widget? icon, + Color? iconColor, + Widget? label, + TextSpan? labelTextSpan, + TextStyle? labelStyle, + TextStyle? floatingLabelStyle, + String? helperText, + TextStyle? helperStyle, + int? helperMaxLines, + TextSpan? hintTextSpan, + TextStyle? hintStyle, + TextDirection? hintTextDirection, + Duration? hintFadeDuration, + int? hintMaxLines, + Widget? error, + String? errorText, + TextStyle? errorStyle, + int? errorMaxLines, + FloatingLabelBehavior? floatingLabelBehavior, + FloatingLabelAlignment? floatingLabelAlignment, + bool? isCollapsed, + bool? isDense, + EdgeInsetsGeometry? contentPadding, + Widget? prefixIcon, + Widget? prefix, + String? prefixText, + BoxConstraints? prefixIconConstraints, + TextStyle? prefixStyle, + Color? prefixIconColor, + Widget? suffixIcon, + Widget? suffix, + String? suffixText, + TextStyle? suffixStyle, + Color? suffixIconColor, + BoxConstraints? suffixIconConstraints, + Widget? counter, + String? counterText, + TextStyle? counterStyle, + bool? filled, + Color? fillColor, + Color? focusColor, + Color? hoverColor, + InputBorder? errorBorder, + InputBorder? focusedBorder, + InputBorder? focusedErrorBorder, + InputBorder? disabledBorder, + InputBorder? enabledBorder, + InputBorder? border, + bool? enabled, + String? semanticCounterText, + bool? alignLabelWithHint, + BoxConstraints? constraints, + }) { + return RichInputDecoration( + icon: icon ?? this.icon, + iconColor: iconColor ?? this.iconColor, + label: label ?? this.label, + labelTextSpan: labelTextSpan ?? this.labelTextSpan, + labelStyle: labelStyle ?? this.labelStyle, + floatingLabelStyle: floatingLabelStyle ?? this.floatingLabelStyle, + helperText: helperText ?? this.helperText, + helperStyle: helperStyle ?? this.helperStyle, + helperMaxLines: helperMaxLines ?? this.helperMaxLines, + hintTextSpan: hintTextSpan ?? this.hintTextSpan, + hintStyle: hintStyle ?? this.hintStyle, + hintTextDirection: hintTextDirection ?? this.hintTextDirection, + hintMaxLines: hintMaxLines ?? this.hintMaxLines, + hintFadeDuration: hintFadeDuration ?? this.hintFadeDuration, + error: error ?? this.error, + errorText: errorText ?? this.errorText, + errorStyle: errorStyle ?? this.errorStyle, + errorMaxLines: errorMaxLines ?? this.errorMaxLines, + floatingLabelBehavior: + floatingLabelBehavior ?? this.floatingLabelBehavior, + floatingLabelAlignment: + floatingLabelAlignment ?? this.floatingLabelAlignment, + isCollapsed: isCollapsed ?? this.isCollapsed, + isDense: isDense ?? this.isDense, + contentPadding: contentPadding ?? this.contentPadding, + prefixIcon: prefixIcon ?? this.prefixIcon, + prefix: prefix ?? this.prefix, + prefixText: prefixText ?? this.prefixText, + prefixStyle: prefixStyle ?? this.prefixStyle, + prefixIconColor: prefixIconColor ?? this.prefixIconColor, + prefixIconConstraints: + prefixIconConstraints ?? this.prefixIconConstraints, + suffixIcon: suffixIcon ?? this.suffixIcon, + suffix: suffix ?? this.suffix, + suffixText: suffixText ?? this.suffixText, + suffixStyle: suffixStyle ?? this.suffixStyle, + suffixIconColor: suffixIconColor ?? this.suffixIconColor, + suffixIconConstraints: + suffixIconConstraints ?? this.suffixIconConstraints, + counter: counter ?? this.counter, + counterText: counterText ?? this.counterText, + counterStyle: counterStyle ?? this.counterStyle, + filled: filled ?? this.filled, + fillColor: fillColor ?? this.fillColor, + focusColor: focusColor ?? this.focusColor, + hoverColor: hoverColor ?? this.hoverColor, + errorBorder: errorBorder ?? this.errorBorder, + focusedBorder: focusedBorder ?? this.focusedBorder, + focusedErrorBorder: focusedErrorBorder ?? this.focusedErrorBorder, + disabledBorder: disabledBorder ?? this.disabledBorder, + enabledBorder: enabledBorder ?? this.enabledBorder, + border: border ?? this.border, + enabled: enabled ?? this.enabled, + semanticCounterText: semanticCounterText ?? this.semanticCounterText, + alignLabelWithHint: alignLabelWithHint ?? this.alignLabelWithHint, + constraints: constraints ?? this.constraints, + ); + } + + /// Used by widgets like [TextField] and [RichInputDecorator] to create a new + /// [RichInputDecoration] with default values taken from the [theme]. + /// + /// Only null valued properties from this [RichInputDecoration] are replaced + /// by the corresponding values from [theme]. + RichInputDecoration applyDefaults(InputDecorationTheme theme) { + return copyWith( + labelStyle: labelStyle ?? theme.labelStyle, + floatingLabelStyle: floatingLabelStyle ?? theme.floatingLabelStyle, + helperStyle: helperStyle ?? theme.helperStyle, + helperMaxLines: helperMaxLines ?? theme.helperMaxLines, + hintStyle: hintStyle ?? theme.hintStyle, + hintFadeDuration: hintFadeDuration ?? theme.hintFadeDuration, + errorStyle: errorStyle ?? theme.errorStyle, + errorMaxLines: errorMaxLines ?? theme.errorMaxLines, + floatingLabelBehavior: + floatingLabelBehavior ?? theme.floatingLabelBehavior, + floatingLabelAlignment: + floatingLabelAlignment ?? theme.floatingLabelAlignment, + isDense: isDense ?? theme.isDense, + contentPadding: contentPadding ?? theme.contentPadding, + isCollapsed: isCollapsed ?? theme.isCollapsed, + iconColor: iconColor ?? theme.iconColor, + prefixStyle: prefixStyle ?? theme.prefixStyle, + prefixIconColor: prefixIconColor ?? theme.prefixIconColor, + suffixStyle: suffixStyle ?? theme.suffixStyle, + suffixIconColor: suffixIconColor ?? theme.suffixIconColor, + counterStyle: counterStyle ?? theme.counterStyle, + filled: filled ?? theme.filled, + fillColor: fillColor ?? theme.fillColor, + focusColor: focusColor ?? theme.focusColor, + hoverColor: hoverColor ?? theme.hoverColor, + errorBorder: errorBorder ?? theme.errorBorder, + focusedBorder: focusedBorder ?? theme.focusedBorder, + focusedErrorBorder: focusedErrorBorder ?? theme.focusedErrorBorder, + disabledBorder: disabledBorder ?? theme.disabledBorder, + enabledBorder: enabledBorder ?? theme.enabledBorder, + border: border ?? theme.border, + alignLabelWithHint: alignLabelWithHint ?? theme.alignLabelWithHint, + constraints: constraints ?? theme.constraints, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is RichInputDecoration && + other.icon == icon && + other.iconColor == iconColor && + other.label == label && + other.labelTextSpan == labelTextSpan && + other.labelStyle == labelStyle && + other.floatingLabelStyle == floatingLabelStyle && + other.helperText == helperText && + other.helperStyle == helperStyle && + other.helperMaxLines == helperMaxLines && + other.hintTextSpan == hintTextSpan && + other.hintStyle == hintStyle && + other.hintTextDirection == hintTextDirection && + other.hintMaxLines == hintMaxLines && + other.hintFadeDuration == hintFadeDuration && + other.error == error && + other.errorText == errorText && + other.errorStyle == errorStyle && + other.errorMaxLines == errorMaxLines && + other.floatingLabelBehavior == floatingLabelBehavior && + other.floatingLabelAlignment == floatingLabelAlignment && + other.isDense == isDense && + other.contentPadding == contentPadding && + other.isCollapsed == isCollapsed && + other.prefixIcon == prefixIcon && + other.prefixIconColor == prefixIconColor && + other.prefix == prefix && + other.prefixText == prefixText && + other.prefixStyle == prefixStyle && + other.prefixIconConstraints == prefixIconConstraints && + other.suffixIcon == suffixIcon && + other.suffixIconColor == suffixIconColor && + other.suffix == suffix && + other.suffixText == suffixText && + other.suffixStyle == suffixStyle && + other.suffixIconConstraints == suffixIconConstraints && + other.counter == counter && + other.counterText == counterText && + other.counterStyle == counterStyle && + other.filled == filled && + other.fillColor == fillColor && + other.focusColor == focusColor && + other.hoverColor == hoverColor && + other.errorBorder == errorBorder && + other.focusedBorder == focusedBorder && + other.focusedErrorBorder == focusedErrorBorder && + other.disabledBorder == disabledBorder && + other.enabledBorder == enabledBorder && + other.border == border && + other.enabled == enabled && + other.semanticCounterText == semanticCounterText && + other.alignLabelWithHint == alignLabelWithHint && + other.constraints == constraints; + } + + @override + int get hashCode { + final List values = [ + icon, + iconColor, + label, + labelTextSpan, + floatingLabelStyle, + labelStyle, + helperText, + helperStyle, + helperMaxLines, + hintTextSpan, + hintStyle, + hintTextDirection, + hintMaxLines, + hintFadeDuration, + error, + errorText, + errorStyle, + errorMaxLines, + floatingLabelBehavior, + floatingLabelAlignment, + isDense, + contentPadding, + isCollapsed, + filled, + fillColor, + focusColor, + hoverColor, + prefixIcon, + prefixIconColor, + prefix, + prefixText, + prefixStyle, + prefixIconConstraints, + suffixIcon, + suffixIconColor, + suffix, + suffixText, + suffixStyle, + suffixIconConstraints, + counter, + counterText, + counterStyle, + errorBorder, + focusedBorder, + focusedErrorBorder, + disabledBorder, + enabledBorder, + border, + enabled, + semanticCounterText, + alignLabelWithHint, + constraints, + ]; + return Object.hashAll(values); + } + + @override + String toString() { + final List description = [ + if (icon != null) 'icon: $icon', + if (iconColor != null) 'iconColor: $iconColor', + if (label != null) 'label: $label', + if (labelTextSpan != null) 'labelTextSpan: "$labelTextSpan"', + if (floatingLabelStyle != null) + 'floatingLabelStyle: "$floatingLabelStyle"', + if (helperText != null) 'helperText: "$helperText"', + if (helperMaxLines != null) 'helperMaxLines: "$helperMaxLines"', + if (hintTextSpan != null) 'hintText: "${hintTextSpan?.text}"', + if (hintMaxLines != null) 'hintMaxLines: "$hintMaxLines"', + if (hintFadeDuration != null) 'hintFadeDuration: "$hintFadeDuration"', + if (error != null) 'error: "$error"', + if (errorText != null) 'errorText: "$errorText"', + if (errorStyle != null) 'errorStyle: "$errorStyle"', + if (errorMaxLines != null) 'errorMaxLines: "$errorMaxLines"', + if (floatingLabelBehavior != null) + 'floatingLabelBehavior: $floatingLabelBehavior', + if (floatingLabelAlignment != null) + 'floatingLabelAlignment: $floatingLabelAlignment', + if (isDense ?? false) 'isDense: $isDense', + if (contentPadding != null) 'contentPadding: $contentPadding', + if (isCollapsed ?? false) 'isCollapsed: $isCollapsed', + if (prefixIcon != null) 'prefixIcon: $prefixIcon', + if (prefixIconColor != null) 'prefixIconColor: $prefixIconColor', + if (prefix != null) 'prefix: $prefix', + if (prefixText != null) 'prefixText: $prefixText', + if (prefixStyle != null) 'prefixStyle: $prefixStyle', + if (prefixIconConstraints != null) + 'prefixIconConstraints: $prefixIconConstraints', + if (suffixIcon != null) 'suffixIcon: $suffixIcon', + if (suffixIconColor != null) 'suffixIconColor: $suffixIconColor', + if (suffix != null) 'suffix: $suffix', + if (suffixText != null) 'suffixText: $suffixText', + if (suffixStyle != null) 'suffixStyle: $suffixStyle', + if (suffixIconConstraints != null) + 'suffixIconConstraints: $suffixIconConstraints', + if (counter != null) 'counter: $counter', + if (counterText != null) 'counterText: $counterText', + if (counterStyle != null) 'counterStyle: $counterStyle', + if (filled ?? false) 'filled: true', + if (fillColor != null) 'fillColor: $fillColor', + if (focusColor != null) 'focusColor: $focusColor', + if (hoverColor != null) 'hoverColor: $hoverColor', + if (errorBorder != null) 'errorBorder: $errorBorder', + if (focusedBorder != null) 'focusedBorder: $focusedBorder', + if (focusedErrorBorder != null) 'focusedErrorBorder: $focusedErrorBorder', + if (disabledBorder != null) 'disabledBorder: $disabledBorder', + if (enabledBorder != null) 'enabledBorder: $enabledBorder', + if (border != null) 'border: $border', + if (!enabled) 'enabled: false', + if (semanticCounterText != null) + 'semanticCounterText: $semanticCounterText', + if (alignLabelWithHint != null) 'alignLabelWithHint: $alignLabelWithHint', + if (constraints != null) 'constraints: $constraints', + ]; + return 'RichInputDecoration(${description.join(', ')})'; + } +} + +/// Defines the default appearance of [RichInputDecorator]s. +/// +/// This class is used to define the value of [ThemeData.inputDecorationTheme]. +/// The [RichInputDecorator], [TextField], and [TextFormField] widgets use +/// the current input decoration theme to initialize null [RichInputDecoration] +/// properties. +/// +/// The [RichInputDecoration.applyDefaults] method is used to combine a input +/// decoration theme with an [RichInputDecoration] object. +@immutable +class InputDecorationTheme with Diagnosticable { + /// Creates a value for [ThemeData.inputDecorationTheme] that + /// defines default values for [RichInputDecorator]. + const InputDecorationTheme({ + this.labelStyle, + this.floatingLabelStyle, + this.helperStyle, + this.helperMaxLines, + this.hintStyle, + this.hintFadeDuration, + this.errorStyle, + this.errorMaxLines, + this.floatingLabelBehavior = FloatingLabelBehavior.auto, + this.floatingLabelAlignment = FloatingLabelAlignment.start, + this.isDense = false, + this.contentPadding, + this.isCollapsed = false, + this.iconColor, + this.prefixStyle, + this.prefixIconColor, + this.suffixStyle, + this.suffixIconColor, + this.counterStyle, + this.filled = false, + this.fillColor, + this.activeIndicatorBorder, + this.outlineBorder, + this.focusColor, + this.hoverColor, + this.errorBorder, + this.focusedBorder, + this.focusedErrorBorder, + this.disabledBorder, + this.enabledBorder, + this.border, + this.alignLabelWithHint = false, + this.constraints, + }); + + /// {@macro flutter.material.inputDecoration.labelStyle} + final TextStyle? labelStyle; + + /// {@macro flutter.material.inputDecoration.floatingLabelStyle} + final TextStyle? floatingLabelStyle; + + /// The style to use for [RichInputDecoration.helperText]. + /// + /// If [helperStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + final TextStyle? helperStyle; + + /// The maximum number of lines the [RichInputDecoration.helperText] can occupy. + /// + /// Defaults to null, which means that the [RichInputDecoration.helperText] will + /// be limited to a single line with [TextOverflow.ellipsis]. + /// + /// This value is passed along to the [Text.maxLines] attribute + /// of the [Text] widget used to display the helper. + /// + /// See also: + /// + /// * [errorMaxLines], the equivalent but for the [RichInputDecoration.errorText]. + final int? helperMaxLines; + + /// The style to use for the [RichInputDecoration.hintText]. + /// + /// If [hintStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// Also used for the [RichInputDecoration.labelTextSpan] when the + /// [RichInputDecoration.labelTextSpan] is displayed on top of the input field (i.e., + /// at the same location on the screen where text may be entered in the input + /// field). + /// + /// If null, defaults to a value derived from the base [TextStyle] for the + /// input field and the current [Theme]. + final TextStyle? hintStyle; + + /// The duration of the [RichInputDecoration.hintText] fade in and fade out animations. + final Duration? hintFadeDuration; + + /// {@macro flutter.material.inputDecoration.errorStyle} + final TextStyle? errorStyle; + + /// The maximum number of lines the [RichInputDecoration.errorText] can occupy. + /// + /// Defaults to null, which means that the [RichInputDecoration.errorText] will be + /// limited to a single line with [TextOverflow.ellipsis]. + /// + /// This value is passed along to the [Text.maxLines] attribute + /// of the [Text] widget used to display the error. + /// + /// See also: + /// + /// * [helperMaxLines], the equivalent but for the [RichInputDecoration.helperText]. + final int? errorMaxLines; + + /// {@macro flutter.material.inputDecoration.floatingLabelBehavior} + /// + /// Defaults to [FloatingLabelBehavior.auto]. + final FloatingLabelBehavior floatingLabelBehavior; + + /// {@macro flutter.material.inputDecoration.floatingLabelAlignment} + /// + /// Defaults to [FloatingLabelAlignment.start]. + final FloatingLabelAlignment floatingLabelAlignment; + + /// Whether the input decorator's child is part of a dense form (i.e., uses + /// less vertical space). + /// + /// Defaults to false. + final bool isDense; + + /// The padding for the input decoration's container. + /// + /// The decoration's container is the area which is filled if + /// [RichInputDecoration.filled] is true and bordered per the [border]. + /// It's the area adjacent to [RichInputDecoration.icon] and above the + /// [RichInputDecoration.icon] and above the widgets that contain + /// [RichInputDecoration.helperText], [RichInputDecoration.errorText], and + /// [RichInputDecoration.counterText]. + /// + /// By default the [contentPadding] reflects [isDense] and the type of the + /// [border]. If [isCollapsed] is true then [contentPadding] is + /// [EdgeInsets.zero]. + final EdgeInsetsGeometry? contentPadding; + + /// Whether the decoration is the same size as the input field. + /// + /// A collapsed decoration cannot have [RichInputDecoration.labelTextSpan], + /// [RichInputDecoration.errorText], or an [RichInputDecoration.icon]. + final bool isCollapsed; + + /// The Color to use for the [RichInputDecoration.icon]. + /// + /// If [iconColor] is a [MaterialStateColor], then the effective + /// color can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// If null, defaults to the [ColorScheme.primary]. + final Color? iconColor; + + /// The style to use for the [RichInputDecoration.prefixText]. + /// + /// If [prefixStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// If null, defaults to the [hintStyle]. + final TextStyle? prefixStyle; + + /// The Color to use for the [RichInputDecoration.prefixIcon]. + /// + /// If [prefixIconColor] is a [MaterialStateColor], then the effective + /// color can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// If null, defaults to the [ColorScheme.primary]. + final Color? prefixIconColor; + + /// The style to use for the [RichInputDecoration.suffixText]. + /// + /// If [suffixStyle] is a [MaterialStateTextStyle], then the effective + /// color can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// If null, defaults to the [hintStyle]. + final TextStyle? suffixStyle; + + /// The Color to use for the [RichInputDecoration.suffixIcon]. + /// + /// If [suffixIconColor] is a [MaterialStateColor], then the effective + /// color can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// If null, defaults to the [ColorScheme.primary]. + final Color? suffixIconColor; + + /// The style to use for the [RichInputDecoration.counterText]. + /// + /// If [counterStyle] is a [MaterialStateTextStyle], then the effective + /// text style can depend on the [MaterialState.focused] state, i.e. + /// if the [TextField] is focused or not. + /// + /// If null, defaults to the [helperStyle]. + final TextStyle? counterStyle; + + /// If true the decoration's container is filled with [fillColor]. + /// + /// Typically this field set to true if [border] is an + /// [UnderlineInputBorder]. + /// + /// The decoration's container is the area, defined by the border's + /// [InputBorder.getOuterPath], which is filled if [filled] is + /// true and bordered per the [border]. + /// + /// This property is false by default. + final bool filled; + + /// The color to fill the decoration's container with, if [filled] is true. + /// + /// By default the fillColor is based on the current [Theme]. + /// + /// The decoration's container is the area, defined by the border's + /// [InputBorder.getOuterPath], which is filled if [filled] is + /// true and bordered per the [border]. + final Color? fillColor; + + /// The borderSide of the OutlineInputBorder with `color` and `weight`. + final BorderSide? outlineBorder; + + /// The borderSide of the UnderlineInputBorder with `color` and `weight`. + final BorderSide? activeIndicatorBorder; + + /// The color to blend with the decoration's [fillColor] with, if [filled] is + /// true and the container has the input focus. + /// + /// By default the [focusColor] is based on the current [Theme]. + /// + /// The decoration's container is the area, defined by the border's + /// [InputBorder.getOuterPath], which is filled if [filled] is + /// true and bordered per the [border]. + final Color? focusColor; + + /// The color to blend with the decoration's [fillColor] with, if the + /// decoration is being hovered over by a mouse pointer. + /// + /// By default the [hoverColor] is based on the current [Theme]. + /// + /// The decoration's container is the area, defined by the border's + /// [InputBorder.getOuterPath], which is filled if [filled] is + /// true and bordered per the [border]. + /// + /// The container will be filled when hovered over even if [filled] is false. + final Color? hoverColor; + + /// The border to display when the [RichInputDecorator] does not have the focus and + /// is showing an error. + /// + /// See also: + /// + /// * [RichInputDecorator.isFocused], which is true if the [RichInputDecorator]'s child + /// has the focus. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [focusedBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [RichInputDecoration.enabled] is false + /// and [RichInputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [RichInputDecoration.enabled] is true + /// and [RichInputDecoration.errorText] is null. + final InputBorder? errorBorder; + + /// The border to display when the [RichInputDecorator] has the focus and is not + /// showing an error. + /// + /// See also: + /// + /// * [RichInputDecorator.isFocused], which is true if the [RichInputDecorator]'s child + /// has the focus. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [RichInputDecorator.isFocused] is false + /// and [RichInputDecoration.errorText] is non-null. + /// * [focusedErrorBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [RichInputDecoration.enabled] is false + /// and [RichInputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [RichInputDecoration.enabled] is true + /// and [RichInputDecoration.errorText] is null. + final InputBorder? focusedBorder; + + /// The border to display when the [RichInputDecorator] has the focus and is + /// showing an error. + /// + /// See also: + /// + /// * [RichInputDecorator.isFocused], which is true if the [RichInputDecorator]'s child + /// has the focus. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [RichInputDecorator.isFocused] is false + /// and [RichInputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is null. + /// * [disabledBorder], displayed when [RichInputDecoration.enabled] is false + /// and [RichInputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [RichInputDecoration.enabled] is true + /// and [RichInputDecoration.errorText] is null. + final InputBorder? focusedErrorBorder; + + /// The border to display when the [RichInputDecorator] is disabled and is not + /// showing an error. + /// + /// See also: + /// + /// * [RichInputDecoration.enabled], which is false if the [RichInputDecorator] is disabled. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [RichInputDecorator.isFocused] is false + /// and [RichInputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is non-null. + /// * [enabledBorder], displayed when [RichInputDecoration.enabled] is true + /// and [RichInputDecoration.errorText] is null. + final InputBorder? disabledBorder; + + /// The border to display when the [RichInputDecorator] is enabled and is not + /// showing an error. + /// + /// See also: + /// + /// * [RichInputDecoration.enabled], which is false if the [RichInputDecorator] is disabled. + /// * [RichInputDecoration.errorText], the error shown by the [RichInputDecorator], if non-null. + /// * [border], for a description of where the [RichInputDecorator] border appears. + /// * [UnderlineInputBorder], an [RichInputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [RichInputDecorator.isFocused] is false + /// and [RichInputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [RichInputDecorator.isFocused] is true + /// and [RichInputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [RichInputDecoration.enabled] is false + /// and [RichInputDecoration.errorText] is null. + final InputBorder? enabledBorder; + + /// The shape of the border to draw around the decoration's container. + /// + /// If [border] is a [MaterialStateUnderlineInputBorder] + /// or [MaterialStateOutlineInputBorder], then the effective border can depend on + /// the [MaterialState.focused] state, i.e. if the [TextField] is focused or not. + /// + /// The decoration's container is the area which is filled if [filled] is + /// true and bordered per the [border]. It's the area adjacent to + /// [RichInputDecoration.icon] and above the widgets that contain + /// [RichInputDecoration.helperText], [RichInputDecoration.errorText], and + /// [RichInputDecoration.counterText]. + /// + /// The border's bounds, i.e. the value of `border.getOuterPath()`, define + /// the area to be filled. + /// + /// This property is only used when the appropriate one of [errorBorder], + /// [focusedBorder], [focusedErrorBorder], [disabledBorder], or [enabledBorder] + /// is not specified. This border's [InputBorder.borderSide] property is + /// configured by the RichInputDecorator, depending on the values of + /// [RichInputDecoration.errorText], [RichInputDecoration.enabled], + /// [RichInputDecorator.isFocused] and the current [Theme]. + /// + /// Typically one of [UnderlineInputBorder] or [OutlineInputBorder]. + /// If null, RichInputDecorator's default is `const UnderlineInputBorder()`. + /// + /// See also: + /// + /// * [InputBorder.none], which doesn't draw a border. + /// * [UnderlineInputBorder], which draws a horizontal line at the + /// bottom of the input decorator's container. + /// * [OutlineInputBorder], an [RichInputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + final InputBorder? border; + + /// Typically set to true when the [RichInputDecorator] contains a multiline + /// [TextField] ([TextField.maxLines] is null or > 1) to override the default + /// behavior of aligning the label with the center of the [TextField]. + final bool alignLabelWithHint; + + /// Defines minimum and maximum sizes for the [RichInputDecorator]. + /// + /// Typically the decorator will fill the horizontal space it is given. For + /// larger screens, it may be useful to have the maximum width clamped to + /// a given value so it doesn't fill the whole screen. This property + /// allows you to control how big the decorator will be in its available + /// space. + /// + /// If null, then the decorator will fill the available width with + /// a default height based on text size. + /// + /// See also: + /// + /// * [RichInputDecoration.constraints], which can override this setting for a + /// given decorator. + final BoxConstraints? constraints; + + /// Creates a copy of this object but with the given fields replaced with the + /// new values. + InputDecorationTheme copyWith({ + TextStyle? labelStyle, + TextStyle? floatingLabelStyle, + TextStyle? helperStyle, + int? helperMaxLines, + TextStyle? hintStyle, + Duration? hintFadeDuration, + TextStyle? errorStyle, + int? errorMaxLines, + FloatingLabelBehavior? floatingLabelBehavior, + FloatingLabelAlignment? floatingLabelAlignment, + bool? isDense, + EdgeInsetsGeometry? contentPadding, + bool? isCollapsed, + Color? iconColor, + TextStyle? prefixStyle, + Color? prefixIconColor, + TextStyle? suffixStyle, + Color? suffixIconColor, + TextStyle? counterStyle, + bool? filled, + Color? fillColor, + BorderSide? activeIndicatorBorder, + BorderSide? outlineBorder, + Color? focusColor, + Color? hoverColor, + InputBorder? errorBorder, + InputBorder? focusedBorder, + InputBorder? focusedErrorBorder, + InputBorder? disabledBorder, + InputBorder? enabledBorder, + InputBorder? border, + bool? alignLabelWithHint, + BoxConstraints? constraints, + }) { + return InputDecorationTheme( + labelStyle: labelStyle ?? this.labelStyle, + floatingLabelStyle: floatingLabelStyle ?? this.floatingLabelStyle, + helperStyle: helperStyle ?? this.helperStyle, + helperMaxLines: helperMaxLines ?? this.helperMaxLines, + hintStyle: hintStyle ?? this.hintStyle, + hintFadeDuration: hintFadeDuration ?? this.hintFadeDuration, + errorStyle: errorStyle ?? this.errorStyle, + errorMaxLines: errorMaxLines ?? this.errorMaxLines, + floatingLabelBehavior: + floatingLabelBehavior ?? this.floatingLabelBehavior, + floatingLabelAlignment: + floatingLabelAlignment ?? this.floatingLabelAlignment, + isDense: isDense ?? this.isDense, + contentPadding: contentPadding ?? this.contentPadding, + iconColor: iconColor, + isCollapsed: isCollapsed ?? this.isCollapsed, + prefixStyle: prefixStyle ?? this.prefixStyle, + prefixIconColor: prefixIconColor ?? this.prefixIconColor, + suffixStyle: suffixStyle ?? this.suffixStyle, + suffixIconColor: suffixIconColor ?? this.suffixIconColor, + counterStyle: counterStyle ?? this.counterStyle, + filled: filled ?? this.filled, + fillColor: fillColor ?? this.fillColor, + activeIndicatorBorder: + activeIndicatorBorder ?? this.activeIndicatorBorder, + outlineBorder: outlineBorder ?? this.outlineBorder, + focusColor: focusColor ?? this.focusColor, + hoverColor: hoverColor ?? this.hoverColor, + errorBorder: errorBorder ?? this.errorBorder, + focusedBorder: focusedBorder ?? this.focusedBorder, + focusedErrorBorder: focusedErrorBorder ?? this.focusedErrorBorder, + disabledBorder: disabledBorder ?? this.disabledBorder, + enabledBorder: enabledBorder ?? this.enabledBorder, + border: border ?? this.border, + alignLabelWithHint: alignLabelWithHint ?? this.alignLabelWithHint, + constraints: constraints ?? this.constraints, + ); + } + + /// Returns a copy of this InputDecorationTheme where the non-null fields in + /// the given InputDecorationTheme override the corresponding nullable fields + /// in this InputDecorationTheme. + /// + /// The non-nullable fields of InputDecorationTheme, such as [floatingLabelBehavior], + /// [isDense], [isCollapsed], [filled], and [alignLabelWithHint] cannot be overridden. + /// + /// In other words, the fields of the provided [InputDecorationTheme] are used to + /// fill in the unspecified and nullable fields of this InputDecorationTheme. + InputDecorationTheme merge(InputDecorationTheme? inputDecorationTheme) { + if (inputDecorationTheme == null) { + return this; + } + return copyWith( + labelStyle: labelStyle ?? inputDecorationTheme.labelStyle, + floatingLabelStyle: + floatingLabelStyle ?? inputDecorationTheme.floatingLabelStyle, + helperStyle: helperStyle ?? inputDecorationTheme.helperStyle, + helperMaxLines: helperMaxLines ?? inputDecorationTheme.helperMaxLines, + hintStyle: hintStyle ?? inputDecorationTheme.hintStyle, + hintFadeDuration: + hintFadeDuration ?? inputDecorationTheme.hintFadeDuration, + errorStyle: errorStyle ?? inputDecorationTheme.errorStyle, + errorMaxLines: errorMaxLines ?? inputDecorationTheme.errorMaxLines, + contentPadding: contentPadding ?? inputDecorationTheme.contentPadding, + iconColor: iconColor ?? inputDecorationTheme.iconColor, + prefixStyle: prefixStyle ?? inputDecorationTheme.prefixStyle, + prefixIconColor: prefixIconColor ?? inputDecorationTheme.prefixIconColor, + suffixStyle: suffixStyle ?? inputDecorationTheme.suffixStyle, + suffixIconColor: suffixIconColor ?? inputDecorationTheme.suffixIconColor, + counterStyle: counterStyle ?? inputDecorationTheme.counterStyle, + fillColor: fillColor ?? inputDecorationTheme.fillColor, + activeIndicatorBorder: + activeIndicatorBorder ?? inputDecorationTheme.activeIndicatorBorder, + outlineBorder: outlineBorder ?? inputDecorationTheme.outlineBorder, + focusColor: focusColor ?? inputDecorationTheme.focusColor, + hoverColor: hoverColor ?? inputDecorationTheme.hoverColor, + errorBorder: errorBorder ?? inputDecorationTheme.errorBorder, + focusedBorder: focusedBorder ?? inputDecorationTheme.focusedBorder, + focusedErrorBorder: + focusedErrorBorder ?? inputDecorationTheme.focusedErrorBorder, + disabledBorder: disabledBorder ?? inputDecorationTheme.disabledBorder, + enabledBorder: enabledBorder ?? inputDecorationTheme.enabledBorder, + border: border ?? inputDecorationTheme.border, + constraints: constraints ?? inputDecorationTheme.constraints, + ); + } + + @override + int get hashCode => Object.hash( + labelStyle, + floatingLabelStyle, + helperStyle, + helperMaxLines, + hintStyle, + errorStyle, + errorMaxLines, + floatingLabelBehavior, + floatingLabelAlignment, + isDense, + contentPadding, + isCollapsed, + iconColor, + prefixStyle, + prefixIconColor, + suffixStyle, + suffixIconColor, + counterStyle, + filled, + Object.hash( + fillColor, + activeIndicatorBorder, + outlineBorder, + focusColor, + hoverColor, + errorBorder, + focusedBorder, + focusedErrorBorder, + disabledBorder, + enabledBorder, + border, + alignLabelWithHint, + constraints, + hintFadeDuration, + ), + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is InputDecorationTheme && + other.labelStyle == labelStyle && + other.floatingLabelStyle == floatingLabelStyle && + other.helperStyle == helperStyle && + other.helperMaxLines == helperMaxLines && + other.hintStyle == hintStyle && + other.hintFadeDuration == hintFadeDuration && + other.errorStyle == errorStyle && + other.errorMaxLines == errorMaxLines && + other.isDense == isDense && + other.contentPadding == contentPadding && + other.isCollapsed == isCollapsed && + other.iconColor == iconColor && + other.prefixStyle == prefixStyle && + other.prefixIconColor == prefixIconColor && + other.suffixStyle == suffixStyle && + other.suffixIconColor == suffixIconColor && + other.counterStyle == counterStyle && + other.floatingLabelBehavior == floatingLabelBehavior && + other.floatingLabelAlignment == floatingLabelAlignment && + other.filled == filled && + other.fillColor == fillColor && + other.activeIndicatorBorder == activeIndicatorBorder && + other.outlineBorder == outlineBorder && + other.focusColor == focusColor && + other.hoverColor == hoverColor && + other.errorBorder == errorBorder && + other.focusedBorder == focusedBorder && + other.focusedErrorBorder == focusedErrorBorder && + other.disabledBorder == disabledBorder && + other.enabledBorder == enabledBorder && + other.border == border && + other.alignLabelWithHint == alignLabelWithHint && + other.constraints == constraints && + other.disabledBorder == disabledBorder; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + const InputDecorationTheme defaultTheme = InputDecorationTheme(); + properties.add(DiagnosticsProperty('labelStyle', labelStyle, + defaultValue: defaultTheme.labelStyle)); + properties.add(DiagnosticsProperty( + 'floatingLabelStyle', floatingLabelStyle, + defaultValue: defaultTheme.floatingLabelStyle)); + properties.add(DiagnosticsProperty('helperStyle', helperStyle, + defaultValue: defaultTheme.helperStyle)); + properties.add(IntProperty('helperMaxLines', helperMaxLines, + defaultValue: defaultTheme.helperMaxLines)); + properties.add(DiagnosticsProperty('hintStyle', hintStyle, + defaultValue: defaultTheme.hintStyle)); + properties.add(DiagnosticsProperty( + 'hintFadeDuration', hintFadeDuration, + defaultValue: defaultTheme.hintFadeDuration)); + properties.add(DiagnosticsProperty('errorStyle', errorStyle, + defaultValue: defaultTheme.errorStyle)); + properties.add(IntProperty('errorMaxLines', errorMaxLines, + defaultValue: defaultTheme.errorMaxLines)); + properties.add(DiagnosticsProperty( + 'floatingLabelBehavior', floatingLabelBehavior, + defaultValue: defaultTheme.floatingLabelBehavior)); + properties.add(DiagnosticsProperty( + 'floatingLabelAlignment', floatingLabelAlignment, + defaultValue: defaultTheme.floatingLabelAlignment)); + properties.add(DiagnosticsProperty('isDense', isDense, + defaultValue: defaultTheme.isDense)); + properties.add(DiagnosticsProperty( + 'contentPadding', contentPadding, + defaultValue: defaultTheme.contentPadding)); + properties.add(DiagnosticsProperty('isCollapsed', isCollapsed, + defaultValue: defaultTheme.isCollapsed)); + properties.add(DiagnosticsProperty('iconColor', iconColor, + defaultValue: defaultTheme.iconColor)); + properties.add(DiagnosticsProperty( + 'prefixIconColor', prefixIconColor, + defaultValue: defaultTheme.prefixIconColor)); + properties.add(DiagnosticsProperty('prefixStyle', prefixStyle, + defaultValue: defaultTheme.prefixStyle)); + properties.add(DiagnosticsProperty( + 'suffixIconColor', suffixIconColor, + defaultValue: defaultTheme.suffixIconColor)); + properties.add(DiagnosticsProperty('suffixStyle', suffixStyle, + defaultValue: defaultTheme.suffixStyle)); + properties.add(DiagnosticsProperty('counterStyle', counterStyle, + defaultValue: defaultTheme.counterStyle)); + properties.add(DiagnosticsProperty('filled', filled, + defaultValue: defaultTheme.filled)); + properties.add(ColorProperty('fillColor', fillColor, + defaultValue: defaultTheme.fillColor)); + properties.add(DiagnosticsProperty( + 'activeIndicatorBorder', activeIndicatorBorder, + defaultValue: defaultTheme.activeIndicatorBorder)); + properties.add(DiagnosticsProperty( + 'outlineBorder', outlineBorder, + defaultValue: defaultTheme.outlineBorder)); + properties.add(ColorProperty('focusColor', focusColor, + defaultValue: defaultTheme.focusColor)); + properties.add(ColorProperty('hoverColor', hoverColor, + defaultValue: defaultTheme.hoverColor)); + properties.add(DiagnosticsProperty('errorBorder', errorBorder, + defaultValue: defaultTheme.errorBorder)); + properties.add(DiagnosticsProperty( + 'focusedBorder', focusedBorder, + defaultValue: defaultTheme.focusedErrorBorder)); + properties.add(DiagnosticsProperty( + 'focusedErrorBorder', focusedErrorBorder, + defaultValue: defaultTheme.focusedErrorBorder)); + properties.add(DiagnosticsProperty( + 'disabledBorder', disabledBorder, + defaultValue: defaultTheme.disabledBorder)); + properties.add(DiagnosticsProperty( + 'enabledBorder', enabledBorder, + defaultValue: defaultTheme.enabledBorder)); + properties.add(DiagnosticsProperty('border', border, + defaultValue: defaultTheme.border)); + properties.add(DiagnosticsProperty( + 'alignLabelWithHint', alignLabelWithHint, + defaultValue: defaultTheme.alignLabelWithHint)); + properties.add(DiagnosticsProperty( + 'constraints', constraints, + defaultValue: defaultTheme.constraints)); + } +} + +class _InputDecoratorDefaultsM2 extends InputDecorationTheme { + const _InputDecoratorDefaultsM2(this.context) : super(); + + final BuildContext context; + + @override + TextStyle? get hintStyle => + MaterialStateTextStyle.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return TextStyle(color: Theme.of(context).disabledColor); + } + return TextStyle(color: Theme.of(context).hintColor); + }); + + @override + TextStyle? get labelStyle => + MaterialStateTextStyle.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return TextStyle(color: Theme.of(context).disabledColor); + } + return TextStyle(color: Theme.of(context).hintColor); + }); + + @override + TextStyle? get floatingLabelStyle => + MaterialStateTextStyle.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return TextStyle(color: Theme.of(context).disabledColor); + } + if (states.contains(MaterialState.error)) { + return TextStyle(color: Theme.of(context).colorScheme.error); + } + if (states.contains(MaterialState.focused)) { + return TextStyle(color: Theme.of(context).colorScheme.primary); + } + return TextStyle(color: Theme.of(context).hintColor); + }); + + @override + TextStyle? get helperStyle => + MaterialStateTextStyle.resolveWith((Set states) { + final ThemeData themeData = Theme.of(context); + if (states.contains(MaterialState.disabled)) { + return themeData.textTheme.bodySmall! + .copyWith(color: Colors.transparent); + } + + return themeData.textTheme.bodySmall! + .copyWith(color: themeData.hintColor); + }); + + @override + TextStyle? get errorStyle => + MaterialStateTextStyle.resolveWith((Set states) { + final ThemeData themeData = Theme.of(context); + if (states.contains(MaterialState.disabled)) { + return themeData.textTheme.bodySmall! + .copyWith(color: Colors.transparent); + } + return themeData.textTheme.bodySmall! + .copyWith(color: themeData.colorScheme.error); + }); + + @override + Color? get fillColor => + MaterialStateColor.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + // dark theme: 5% white + // light theme: 2% black + switch (Theme.of(context).brightness) { + case Brightness.dark: + return const Color(0x0DFFFFFF); + case Brightness.light: + return const Color(0x05000000); + } + } + // dark theme: 10% white + // light theme: 4% black + switch (Theme.of(context).brightness) { + case Brightness.dark: + return const Color(0x1AFFFFFF); + case Brightness.light: + return const Color(0x0A000000); + } + }); + + @override + Color? get iconColor => + MaterialStateColor.resolveWith((Set states) { + if (states.contains(MaterialState.disabled) && + !states.contains(MaterialState.focused)) { + return Theme.of(context).disabledColor; + } + if (states.contains(MaterialState.focused)) { + return Theme.of(context).colorScheme.primary; + } + switch (Theme.of(context).brightness) { + case Brightness.dark: + return Colors.white70; + case Brightness.light: + return Colors.black45; + } + }); + + @override + Color? get prefixIconColor => + MaterialStateColor.resolveWith((Set states) { + if (states.contains(MaterialState.disabled) && + !states.contains(MaterialState.focused)) { + return Theme.of(context).disabledColor; + } + if (states.contains(MaterialState.focused)) { + return Theme.of(context).colorScheme.primary; + } + switch (Theme.of(context).brightness) { + case Brightness.dark: + return Colors.white70; + case Brightness.light: + return Colors.black45; + } + }); + + @override + Color? get suffixIconColor => + MaterialStateColor.resolveWith((Set states) { + if (states.contains(MaterialState.disabled) && + !states.contains(MaterialState.focused)) { + return Theme.of(context).disabledColor; + } + if (states.contains(MaterialState.focused)) { + return Theme.of(context).colorScheme.primary; + } + switch (Theme.of(context).brightness) { + case Brightness.dark: + return Colors.white70; + case Brightness.light: + return Colors.black45; + } + }); +} + +// BEGIN GENERATED TOKEN PROPERTIES - RichInputDecorator + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +class _InputDecoratorDefaultsM3 extends InputDecorationTheme { + _InputDecoratorDefaultsM3(this.context) : super(); + + final BuildContext context; + + late final ColorScheme _colors = Theme.of(context).colorScheme; + late final TextTheme _textTheme = Theme.of(context).textTheme; + + @override + TextStyle? get hintStyle => + MaterialStateTextStyle.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return TextStyle(color: Theme.of(context).disabledColor); + } + return TextStyle(color: Theme.of(context).hintColor); + }); + + @override + Color? get fillColor => + MaterialStateColor.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return _colors.onSurface.withOpacity(0.04); + } + return _colors.surfaceVariant; + }); + + @override + BorderSide? get activeIndicatorBorder => + MaterialStateBorderSide.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return BorderSide(color: _colors.onSurface.withOpacity(0.38)); + } + if (states.contains(MaterialState.error)) { + if (states.contains(MaterialState.hovered)) { + return BorderSide(color: _colors.onErrorContainer); + } + if (states.contains(MaterialState.focused)) { + return BorderSide(color: _colors.error, width: 2.0); + } + return BorderSide(color: _colors.error); + } + if (states.contains(MaterialState.hovered)) { + return BorderSide(color: _colors.onSurface); + } + if (states.contains(MaterialState.focused)) { + return BorderSide(color: _colors.primary, width: 2.0); + } + return BorderSide(color: _colors.onSurfaceVariant); + }); + + @override + BorderSide? get outlineBorder => + MaterialStateBorderSide.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return BorderSide(color: _colors.onSurface.withOpacity(0.12)); + } + if (states.contains(MaterialState.error)) { + if (states.contains(MaterialState.hovered)) { + return BorderSide(color: _colors.onErrorContainer); + } + if (states.contains(MaterialState.focused)) { + return BorderSide(color: _colors.error, width: 2.0); + } + return BorderSide(color: _colors.error); + } + if (states.contains(MaterialState.hovered)) { + return BorderSide(color: _colors.onSurface); + } + if (states.contains(MaterialState.focused)) { + return BorderSide(color: _colors.primary, width: 2.0); + } + return BorderSide(color: _colors.outline); + }); + + @override + Color? get iconColor => _colors.onSurfaceVariant; + + @override + Color? get prefixIconColor => + MaterialStateColor.resolveWith((Set states) { + return _colors.onSurfaceVariant; + }); + + @override + Color? get suffixIconColor => + MaterialStateColor.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return _colors.onSurface.withOpacity(0.38); + } + if (states.contains(MaterialState.error)) { + return _colors.error; + } + return _colors.onSurfaceVariant; + }); + + @override + TextStyle? get labelStyle => + MaterialStateTextStyle.resolveWith((Set states) { + final TextStyle textStyle = _textTheme.bodyLarge ?? const TextStyle(); + if (states.contains(MaterialState.disabled)) { + return textStyle.copyWith(color: _colors.onSurface.withOpacity(0.38)); + } + if (states.contains(MaterialState.error)) { + if (states.contains(MaterialState.hovered)) { + return textStyle.copyWith(color: _colors.onErrorContainer); + } + if (states.contains(MaterialState.focused)) { + return textStyle.copyWith(color: _colors.error); + } + return textStyle.copyWith(color: _colors.error); + } + if (states.contains(MaterialState.hovered)) { + return textStyle.copyWith(color: _colors.onSurfaceVariant); + } + if (states.contains(MaterialState.focused)) { + return textStyle.copyWith(color: _colors.primary); + } + return textStyle.copyWith(color: _colors.onSurfaceVariant); + }); + + @override + TextStyle? get floatingLabelStyle => + MaterialStateTextStyle.resolveWith((Set states) { + final TextStyle textStyle = _textTheme.bodyLarge ?? const TextStyle(); + if (states.contains(MaterialState.disabled)) { + return textStyle.copyWith(color: _colors.onSurface.withOpacity(0.38)); + } + if (states.contains(MaterialState.error)) { + if (states.contains(MaterialState.hovered)) { + return textStyle.copyWith(color: _colors.onErrorContainer); + } + if (states.contains(MaterialState.focused)) { + return textStyle.copyWith(color: _colors.error); + } + return textStyle.copyWith(color: _colors.error); + } + if (states.contains(MaterialState.hovered)) { + return textStyle.copyWith(color: _colors.onSurfaceVariant); + } + if (states.contains(MaterialState.focused)) { + return textStyle.copyWith(color: _colors.primary); + } + return textStyle.copyWith(color: _colors.onSurfaceVariant); + }); + + @override + TextStyle? get helperStyle => + MaterialStateTextStyle.resolveWith((Set states) { + final TextStyle textStyle = _textTheme.bodySmall ?? const TextStyle(); + if (states.contains(MaterialState.disabled)) { + return textStyle.copyWith(color: _colors.onSurface.withOpacity(0.38)); + } + return textStyle.copyWith(color: _colors.onSurfaceVariant); + }); + + @override + TextStyle? get errorStyle => + MaterialStateTextStyle.resolveWith((Set states) { + final TextStyle textStyle = _textTheme.bodySmall ?? const TextStyle(); + return textStyle.copyWith(color: _colors.error); + }); +} + +// END GENERATED TOKEN PROPERTIES - RichInputDecorator diff --git a/lib/src/view/textfield/rtf_text_field.dart b/lib/src/view/textfield/rtf_text_field.dart new file mode 100644 index 0000000..6d87b75 --- /dev/null +++ b/lib/src/view/textfield/rtf_text_field.dart @@ -0,0 +1,155 @@ +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:rtf_textfield/rtf_textfield.dart'; + +/// A `RTFTextField` that uses a `RTFTextFieldController` to control the text. +/// The only relevance of this widget over `RTFTextField` is that it listens to changes in the controller and rebuilds on text align change. +class RTFTextField extends RichTextField { + @override + // ignore: overridden_fields + final RTFTextFieldController controller; + + const RTFTextField({ + super.key, + required this.controller, + super.focusNode, + super.decoration = const RichInputDecoration(), + TextInputType? keyboardType, + super.textInputAction, + super.textCapitalization = TextCapitalization.none, + super.style, + super.strutStyle, + super.textAlign = TextAlign.start, + super.textAlignVertical, + super.textDirection, + super.readOnly = false, + super.showCursor, + super.autofocus = false, + super.obscuringCharacter = '•', + super.obscureText = false, + super.autocorrect = true, + SmartDashesType? smartDashesType, + SmartQuotesType? smartQuotesType, + super.enableSuggestions = true, + super.maxLines = 1, + super.minLines, + super.expands = false, + super.maxLength, + super.maxLengthEnforcement, + super.onChanged, + super.onEditingComplete, + super.onSubmitted, + super.onAppPrivateCommand, + super.inputFormatters, + super.enabled, + super.cursorWidth = 2.0, + super.cursorHeight, + super.cursorRadius, + super.cursorColor, + super.selectionHeightStyle = BoxHeightStyle.tight, + super.selectionWidthStyle = BoxWidthStyle.tight, + super.keyboardAppearance, + super.scrollPadding = const EdgeInsets.all(20.0), + super.dragStartBehavior = DragStartBehavior.start, + bool? enableInteractiveSelection, + super.selectionControls, + super.onTap, + super.onTapOutside, + super.mouseCursor, + super.buildCounter, + super.scrollController, + super.scrollPhysics, + super.autofillHints = const [], + super.clipBehavior = Clip.hardEdge, + super.restorationId, + super.scribbleEnabled = true, + super.enableIMEPersonalizedLearning = true, + super.contextMenuBuilder, + super.spellCheckConfiguration, + super.magnifierConfiguration, + }); + + @override + State createState() => _RTFTextFieldState(); +} + +class _RTFTextFieldState extends State { + late RTFTextFieldController controller = widget.controller; + + @override + Widget build(BuildContext context) { + ///value listenable builder added to listen to changes in the controller and rebuild on text align change + return ValueListenableBuilder( + valueListenable: controller, + builder: (_, controllerValue, __) { + return RichTextField( + keyboardType: widget.keyboardType, + key: widget.key, + controller: widget.controller, + focusNode: widget.focusNode, + decoration: widget.decoration, + textInputAction: widget.textInputAction, + textCapitalization: widget.textCapitalization, + style: widget.style, + strutStyle: widget.strutStyle, + textAlign: controller.metadata?.alignment ?? widget.textAlign, + textAlignVertical: widget.textAlignVertical, + textDirection: widget.textDirection, + readOnly: widget.readOnly, + showCursor: widget.showCursor, + autofocus: widget.autofocus, + obscuringCharacter: widget.obscuringCharacter, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + enableSuggestions: widget.enableSuggestions, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + maxLength: widget.maxLength, + maxLengthEnforcement: widget.maxLengthEnforcement, + onChanged: widget.onChanged, + onEditingComplete: widget.onEditingComplete, + onSubmitted: widget.onSubmitted, + onAppPrivateCommand: widget.onAppPrivateCommand, + inputFormatters: widget.inputFormatters, + enabled: widget.enabled, + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: widget.cursorRadius, + cursorColor: widget.cursorColor, + selectionHeightStyle: widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + keyboardAppearance: widget.keyboardAppearance, + scrollPadding: widget.scrollPadding, + dragStartBehavior: widget.dragStartBehavior, + enableInteractiveSelection: widget.enableInteractiveSelection, + selectionControls: widget.selectionControls, + onTap: widget.onTap, + onTapOutside: widget.onTapOutside, + mouseCursor: widget.mouseCursor, + buildCounter: widget.buildCounter, + scrollController: widget.scrollController, + scrollPhysics: widget.scrollPhysics, + autofillHints: widget.autofillHints, + clipBehavior: widget.clipBehavior, + restorationId: widget.restorationId, + scribbleEnabled: widget.scribbleEnabled, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + contextMenuBuilder: widget.contextMenuBuilder, + spellCheckConfiguration: widget.spellCheckConfiguration, + magnifierConfiguration: widget.magnifierConfiguration, + ); + }, + ); + } + + @override + void didUpdateWidget(RTFTextField oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.controller != widget.controller) { + controller = widget.controller; + } + } +} diff --git a/lib/src/view/textfield/rtf_text_form_field.dart b/lib/src/view/textfield/rtf_text_form_field.dart new file mode 100644 index 0000000..a34378c --- /dev/null +++ b/lib/src/view/textfield/rtf_text_form_field.dart @@ -0,0 +1,199 @@ +// ignore_for_file: overridden_fields, annotate_overrides + +import 'package:flutter/material.dart' hide InputCounterWidgetBuilder; +import 'package:flutter/services.dart'; +import 'package:rtf_textfield/rtf_textfield.dart'; + +/// A [TextFormField] that uses a [RTFTextFieldController] to control the text. +/// The only relevance of this widget over [TextFormField] is that it listens to changes in the controller and rebuilds on text align change. +class RTFTextFormField extends RichTextFormField { + @override + final RTFTextFieldController controller; + + final FocusNode? focusNode; + final RichInputDecoration? decoration; + final TextInputType? keyboardType; + final TextCapitalization textCapitalization; + final TextInputAction? textInputAction; + final TextStyle? style; + final StrutStyle? strutStyle; + final TextDirection? textDirection; + final TextAlign textAlign; + final TextAlignVertical? textAlignVertical; + final bool autofocus; + final bool readOnly; + final bool? showCursor; + final String obscuringCharacter; + final bool obscureText; + final bool autocorrect; + final SmartDashesType? smartDashesType; + final SmartQuotesType? smartQuotesType; + final bool enableSuggestions; + final MaxLengthEnforcement? maxLengthEnforcement; + final int? maxLines; + final int? minLines; + final bool expands; + final int? maxLength; + final ValueChanged? onChanged; + final GestureTapCallback? onTap; + final TapRegionCallback? onTapOutside; + final VoidCallback? onEditingComplete; + final ValueChanged? onFieldSubmitted; + final List? inputFormatters; + final double cursorWidth; + final double? cursorHeight; + final Radius? cursorRadius; + final Color? cursorColor; + final Brightness? keyboardAppearance; + final EdgeInsets scrollPadding; + final bool? enableInteractiveSelection; + final TextSelectionControls? selectionControls; + final InputCounterWidgetBuilder? buildCounter; + final ScrollPhysics? scrollPhysics; + final Iterable? autofillHints; + final ScrollController? scrollController; + final bool enableIMEPersonalizedLearning; + final MouseCursor? mouseCursor; + final EditableTextContextMenuBuilder? contextMenuBuilder; + + RTFTextFormField({ + required this.controller, + super.initialValue, + super.autovalidateMode, + this.focusNode, + this.decoration = const RichInputDecoration(), + this.keyboardType, + this.textCapitalization = TextCapitalization.none, + this.textInputAction, + this.style, + this.strutStyle, + this.textDirection, + this.textAlign = TextAlign.start, + this.textAlignVertical, + this.autofocus = false, + this.readOnly = false, + this.showCursor, + this.obscuringCharacter = '•', + this.obscureText = false, + this.autocorrect = true, + this.smartDashesType, + this.smartQuotesType, + this.enableSuggestions = true, + this.maxLengthEnforcement, + this.maxLines = 1, + this.minLines, + this.expands = false, + this.maxLength, + this.onChanged, + this.onTap, + this.onTapOutside, + this.onEditingComplete, + this.onFieldSubmitted, + this.inputFormatters, + super.enabled, + this.cursorWidth = 2.0, + this.cursorHeight, + this.cursorRadius, + this.cursorColor, + this.keyboardAppearance, + this.scrollPadding = const EdgeInsets.all(20.0), + this.enableInteractiveSelection, + this.selectionControls, + this.buildCounter, + this.scrollPhysics, + this.autofillHints, + this.scrollController, + this.enableIMEPersonalizedLearning = true, + this.mouseCursor, + this.contextMenuBuilder, + super.key, + super.onSaved, + super.validator, + }); + + @override + FormFieldState createState() => _RTFTextFormFieldState(); +} + +class _RTFTextFormFieldState extends FormFieldState { + late RTFTextFieldController controller = _richTextFormField.controller; + + RTFTextFormField get _richTextFormField => super.widget as RTFTextFormField; + + @override + void didUpdateWidget(RTFTextFormField oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.controller != _richTextFormField.controller) { + controller = _richTextFormField.controller; + } + } + + @override + Widget build(BuildContext context) { + ///value listenable builder added to listen to changes in the controller and rebuild on text align change + return ValueListenableBuilder( + valueListenable: controller, + builder: (_, controllerValue, __) { + return RichTextFormField( + ///this is the only line that is different from the original text field + textAlign: + controller.metadata?.alignment ?? _richTextFormField.textAlign, + key: _richTextFormField.key, + controller: controller, + focusNode: _richTextFormField.focusNode, + decoration: _richTextFormField.decoration, + keyboardType: _richTextFormField.keyboardType, + textInputAction: _richTextFormField.textInputAction, + textCapitalization: _richTextFormField.textCapitalization, + style: _richTextFormField.style, + strutStyle: _richTextFormField.strutStyle, + textAlignVertical: _richTextFormField.textAlignVertical, + textDirection: _richTextFormField.textDirection, + readOnly: _richTextFormField.readOnly, + showCursor: _richTextFormField.showCursor, + autofocus: _richTextFormField.autofocus, + obscuringCharacter: _richTextFormField.obscuringCharacter, + obscureText: _richTextFormField.obscureText, + autocorrect: _richTextFormField.autocorrect, + smartDashesType: _richTextFormField.smartDashesType, + smartQuotesType: _richTextFormField.smartQuotesType, + enableSuggestions: _richTextFormField.enableSuggestions, + maxLines: _richTextFormField.maxLines, + minLines: _richTextFormField.minLines, + expands: _richTextFormField.expands, + maxLength: _richTextFormField.maxLength, + maxLengthEnforcement: _richTextFormField.maxLengthEnforcement, + onChanged: _richTextFormField.onChanged, + onTap: _richTextFormField.onTap, + onTapOutside: _richTextFormField.onTapOutside, + onEditingComplete: _richTextFormField.onEditingComplete, + onFieldSubmitted: _richTextFormField.onFieldSubmitted, + onSaved: _richTextFormField.onSaved, + validator: _richTextFormField.validator, + inputFormatters: _richTextFormField.inputFormatters, + enabled: _richTextFormField.enabled, + cursorWidth: _richTextFormField.cursorWidth, + cursorHeight: _richTextFormField.cursorHeight, + cursorRadius: _richTextFormField.cursorRadius, + cursorColor: _richTextFormField.cursorColor, + keyboardAppearance: _richTextFormField.keyboardAppearance, + scrollPadding: _richTextFormField.scrollPadding, + enableInteractiveSelection: + _richTextFormField.enableInteractiveSelection, + selectionControls: _richTextFormField.selectionControls, + buildCounters: _richTextFormField.buildCounter, + scrollController: _richTextFormField.scrollController, + scrollPhysics: _richTextFormField.scrollPhysics, + autofillHints: _richTextFormField.autofillHints, + contextMenuBuilder: _richTextFormField.contextMenuBuilder, + autovalidateMode: _richTextFormField.autovalidateMode, + enableIMEPersonalizedLearning: + _richTextFormField.enableIMEPersonalizedLearning, + initialValue: _richTextFormField.initialValue, + mouseCursor: _richTextFormField.mouseCursor, + restorationId: _richTextFormField.restorationId, + ); + }, + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..960aa6c --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,19 @@ +name: rtf_textfield +description: Flutter custom widget to create Rich TextField. It includes textspan for hint, label. Control the text style with RichTextController. +version: 1.0.1 +homepage: https://github.com/K1yoshiSho/rtf_textfield.git + +environment: + sdk: '>=3.2.5 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: \ No newline at end of file diff --git a/test/rich_textfield_test.dart b/test/rich_textfield_test.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/test/rich_textfield_test.dart @@ -0,0 +1 @@ +void main() {}