diff --git a/CHANGELOG.md b/CHANGELOG.md index 41ef1c2..2183d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [2.0.4] - 2022/05/14 + +* Added cursorColor +* Added null check for boxDecoration + +## [2.0.3] - 2022/05/14 + +* Added customizations for decoration of TextFormField +* Added separator between TextFormField and Actions + ## [2.0.2] - 2021/10/26 * Add a controller parameter diff --git a/README.md b/README.md index 5e7cf07..6a68b07 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,14 @@ MarkdownEditableTextInput is a TextField Widget that allow you to convert easily what's in the TextField to Markdown. ## Features +- [x] Customization of input and actions - [x] Convert to Bold, Italic, Strikethrough - [x] Convert to Code, Quote, Links - [x] Convert to Heading (H1, H2, H3, H4, H5, H6) and Links - [x] Support text direction ## Demo -![](pictures/test_edition.gif) +![](pictures/preview.gif) ## Usage The color of the MarkdownTextInput is defined by the color set in your Theme : @@ -35,5 +36,3 @@ The color of the MarkdownTextInput is defined by the color set in your Theme : ### Example You can see an example of how to use this package [here](https://github.com/playmoweb/markdown-editable-textinput/tree/master/example) - - diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 7970551..da6f536 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -135,7 +135,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..3db53b6 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + diff --git a/example/lib/main.dart b/example/lib/main.dart index 1bf2803..ca23a2a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,7 +30,8 @@ class _MyAppState extends State { home: Theme( data: ThemeData( primaryColor: const Color(0xFF2C1C6B), - colorScheme: ColorScheme.light().copyWith(secondary: const Color(0xFF200681)), + colorScheme: + ColorScheme.light().copyWith(secondary: const Color(0xFF200681)), cardColor: const Color(0xFFF8F9FC), textTheme: const TextTheme(bodyText2: TextStyle(fontSize: 20)), ), @@ -51,11 +52,46 @@ class _MyAppState extends State { children: [ MarkdownTextInput( (String value) => setState(() => description = value), - description, - label: 'Description', maxLines: 10, - actions: MarkdownType.values, controller: controller, + actionsBoxDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.lightBlue), + ), + actions: MarkdownType.values, + fillActions: false, + inputDecoration: InputDecoration( + hintText: + 'I have been working for the weekend, but this weekend never came..', + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 16, + fontWeight: FontWeight.w400, + ), + errorStyle: TextStyle( + color: Colors.red, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + hintMaxLines: 2, + errorMaxLines: 2, + fillColor: Colors.white, + border: OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: Color(0xFF5856D6), + ), + borderRadius: BorderRadius.circular(15), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: Color(0xFF5856D6), + ), + borderRadius: BorderRadius.circular(15), + ), + contentPadding: const EdgeInsets.all(20), + ), ), TextButton( onPressed: () { diff --git a/example/pubspec.lock b/example/pubspec.lock index b6a83a4..a1f95e1 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -14,7 +14,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -49,7 +49,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" cupertino_icons: dependency: "direct main" description: @@ -77,7 +77,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" flutter: dependency: "direct main" description: flutter @@ -89,7 +89,7 @@ packages: name: flutter_markdown url: "https://pub.dartlang.org" source: hosted - version: "0.6.7" + version: "0.6.10" flutter_test: dependency: "direct dev" description: flutter @@ -101,21 +101,28 @@ packages: name: markdown url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "5.0.0" markdown_editable_textinput: dependency: "direct main" description: path: ".." relative: true source: path - version: "2.0.1" + version: "2.0.4" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" meta: dependency: transitive description: @@ -129,7 +136,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" sky_engine: dependency: transitive description: flutter @@ -141,7 +148,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -176,21 +183,14 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "0.4.9" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 72de1f6..19139cb 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -17,20 +17,17 @@ dependencies: cupertino_icons: ^0.1.2 markdown_editable_textinput: path: ../ - flutter_markdown: ^0.6.7 - + flutter_markdown: ^0.6.10 dev_dependencies: flutter_test: sdk: flutter - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index d7644f8..6d2b280 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -1,21 +1,18 @@ +import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:markdown_editable_textinput/format_markdown.dart'; -import 'package:expandable/expandable.dart'; /// Widget with markdown buttons class MarkdownTextInput extends StatefulWidget { /// Callback called when text changed - final Function onTextChanged; + final Function(String text) onTextChanged; /// Initial value you want to display - final String initialValue; + final String? initialValue; - /// Validator for the TextFormField + /// `Validator` for the `TextFormField` final String? Function(String? value)? validators; - /// String displayed at hintText in TextFormField - final String? label; - /// Change the text direction of the input (RTL / LTR) final TextDirection? textDirection; @@ -25,14 +22,32 @@ class MarkdownTextInput extends StatefulWidget { /// List of action the component can handle final List actions; - /// Optionnal controller to manage the input + /// Optional `controller` to manage the input final TextEditingController? controller; + /// `InputDecoration` of `TextFormField` + final InputDecoration? inputDecoration; + + /// Expands actions widget to full size + final bool fillActions; + + /// `BoxDecoration` of actions `Container` + final BoxDecoration? actionsBoxDecoration; + + /// Space between `actions` widget and `TextFormField` widget + final double? separaterHeight; + + /// `Padding` of `Actions button` widget + final EdgeInsets actionsButtonPadding; + + /// Color of `cursorColor` + final Color? cursorColor; + /// Constructor for [MarkdownTextInput] MarkdownTextInput( - this.onTextChanged, - this.initialValue, { - this.label = '', + this.onTextChanged, { + this.initialValue, + this.controller, this.validators, this.textDirection = TextDirection.ltr, this.maxLines = 10, @@ -43,40 +58,52 @@ class MarkdownTextInput extends StatefulWidget { MarkdownType.link, MarkdownType.list ], - this.controller, + this.inputDecoration, + this.fillActions = false, + this.actionsBoxDecoration, + this.separaterHeight = 15, + this.actionsButtonPadding = const EdgeInsets.all(15), + this.cursorColor, }); @override - _MarkdownTextInputState createState() => _MarkdownTextInputState(controller ?? TextEditingController()); + _MarkdownTextInputState createState() => + _MarkdownTextInputState(controller ?? TextEditingController()); } class _MarkdownTextInputState extends State { final TextEditingController _controller; - TextSelection textSelection = const TextSelection(baseOffset: 0, extentOffset: 0); + TextSelection textSelection = + const TextSelection(baseOffset: 0, extentOffset: 0); _MarkdownTextInputState(this._controller); void onTap(MarkdownType type, {int titleSize = 1}) { final basePosition = textSelection.baseOffset; - var noTextSelected = (textSelection.baseOffset - textSelection.extentOffset) == 0; + var noTextSelected = + (textSelection.baseOffset - textSelection.extentOffset) == 0; - final result = FormatMarkdown.convertToMarkdown( - type, _controller.text, textSelection.baseOffset, textSelection.extentOffset, + final result = FormatMarkdown.convertToMarkdown(type, _controller.text, + textSelection.baseOffset, textSelection.extentOffset, titleSize: titleSize); - _controller.value = _controller.value - .copyWith(text: result.data, selection: TextSelection.collapsed(offset: basePosition + result.cursorIndex)); + _controller.value = _controller.value.copyWith( + text: result.data, + selection: + TextSelection.collapsed(offset: basePosition + result.cursorIndex)); if (noTextSelected) { - _controller.selection = TextSelection.collapsed(offset: _controller.selection.end - result.replaceCursorIndex); + _controller.selection = TextSelection.collapsed( + offset: _controller.selection.end - result.replaceCursorIndex); } } @override void initState() { - _controller.text = widget.initialValue; + _controller.text = widget.initialValue ?? ''; _controller.addListener(() { - if (_controller.selection.baseOffset != -1) textSelection = _controller.selection; + if (_controller.selection.baseOffset != -1) + textSelection = _controller.selection; widget.onTextChanged(_controller.text); }); super.initState(); @@ -90,39 +117,18 @@ class _MarkdownTextInputState extends State { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - border: Border.all(color: Theme.of(context).colorScheme.secondary, width: 2), - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - child: Column( - children: [ - TextFormField( - textInputAction: TextInputAction.newline, - maxLines: widget.maxLines, - controller: _controller, - textCapitalization: TextCapitalization.sentences, - validator: (value) => widget.validators!(value), - cursorColor: Theme.of(context).primaryColor, - textDirection: widget.textDirection, - decoration: InputDecoration( - enabledBorder: - UnderlineInputBorder(borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary)), - focusedBorder: - UnderlineInputBorder(borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary)), - hintText: widget.label, - hintStyle: const TextStyle(color: Color.fromRGBO(63, 61, 86, 0.5)), - contentPadding: const EdgeInsets.symmetric(vertical: 15, horizontal: 10), - ), - ), - SizedBox( - height: 44, - child: Material( - color: Theme.of(context).cardColor, - borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(10), bottomRight: Radius.circular(10)), - child: ListView( - scrollDirection: Axis.horizontal, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + clipBehavior: Clip.hardEdge, + decoration: widget.actionsBoxDecoration ?? BoxDecoration(), + width: widget.fillActions ? double.maxFinite : null, + child: Material( + color: Colors.transparent, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( children: widget.actions.map((type) { return type == MarkdownType.title ? ExpandableNotifier( @@ -134,36 +140,45 @@ class _MarkdownTextInputState extends State { padding: EdgeInsets.all(10), child: Text( 'H#', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700), ), ), ), ), expanded: Container( color: Colors.white10, - child: Row( - children: [ - for (int i = 1; i <= 6; i++) - InkWell( - key: Key('H${i}_button'), - onTap: () => onTap(MarkdownType.title, titleSize: i), - child: Padding( - padding: const EdgeInsets.all(10), - child: Text( - 'H$i', - style: TextStyle(fontSize: (18 - i).toDouble(), fontWeight: FontWeight.w700), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: NeverScrollableScrollPhysics(), + child: Row( + children: [ + for (int i = 1; i <= 6; i++) + InkWell( + key: Key('H${i}_button'), + onTap: () => onTap(MarkdownType.title, + titleSize: i), + child: Padding( + padding: widget.actionsButtonPadding, + child: Text( + 'H$i', + style: TextStyle( + fontSize: (18 - i).toDouble(), + fontWeight: FontWeight.w700), + ), ), ), - ), - ExpandableButton( - child: const Padding( - padding: EdgeInsets.all(10), - child: Icon( - Icons.close, + ExpandableButton( + child: Padding( + padding: widget.actionsButtonPadding, + child: Icon( + Icons.close, + ), ), ), - ), - ], + ], + ), ), ), ), @@ -172,16 +187,27 @@ class _MarkdownTextInputState extends State { key: Key(type.key), onTap: () => onTap(type), child: Padding( - padding: EdgeInsets.all(10), + padding: widget.actionsButtonPadding, child: Icon(type.icon), ), ); }).toList(), ), ), - ) - ], - ), + ), + ), + SizedBox(height: widget.separaterHeight), + TextFormField( + textInputAction: TextInputAction.newline, + maxLines: widget.maxLines, + controller: _controller, + textCapitalization: TextCapitalization.sentences, + validator: (value) => widget.validators!(value), + textDirection: widget.textDirection, + cursorColor: widget.cursorColor, + decoration: widget.inputDecoration, + ), + ], ); } } diff --git a/pictures/preview.gif b/pictures/preview.gif new file mode 100644 index 0000000..35655aa Binary files /dev/null and b/pictures/preview.gif differ diff --git a/pictures/test_edition.gif b/pictures/test_edition.gif deleted file mode 100644 index fb85008..0000000 Binary files a/pictures/test_edition.gif and /dev/null differ diff --git a/pubspec.lock b/pubspec.lock index 0885c59..20d30f1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" boolean_selector: dependency: transitive description: @@ -21,7 +21,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -42,7 +42,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" effective_dart: dependency: "direct main" description: @@ -63,7 +63,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" flutter: dependency: "direct main" description: flutter @@ -80,7 +80,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" meta: dependency: transitive description: @@ -94,7 +101,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" sky_engine: dependency: transitive description: flutter @@ -106,7 +113,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -141,21 +148,14 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "0.4.9" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index cbecc6b..15ba2ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,8 @@ name: markdown_editable_textinput description: A TextField Widget that allow you to convert easily what's in the TextField to Markdown. -version: 2.0.2 -homepage: https://github.com/playmoweb/markdown-editable-textinput -repository: https://github.com/playmoweb/markdown-editable-textinput +version: 2.0.4 +homepage: https://github.com/maripbekoff/markdown-editable-textinput +repository: https://github.com/maripbekoff/markdown-editable-textinput environment: sdk: '>=2.12.0 <3.0.0' @@ -13,7 +13,6 @@ dependencies: effective_dart: ^1.3.1 expandable: ^5.0.1 - dev_dependencies: flutter_test: sdk: flutter diff --git a/test/markdown_text_input_test.dart b/test/markdown_text_input_test.dart index b2f8795..d81bbb5 100644 --- a/test/markdown_text_input_test.dart +++ b/test/markdown_text_input_test.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:markdown_editable_textinput/markdown_text_input.dart'; void main() { testWidgets('MarkdownTextInput has all buttons', (WidgetTester tester) async { - await tester.pumpWidget(MaterialApp(home: Scaffold(body: MarkdownTextInput(print, 'initial value')))); + await tester.pumpWidget(MaterialApp(home: Scaffold(body: MarkdownTextInput(print, initialValue: 'initial value')))); final boldKey = const Key('bold_button'); final italicKey = const Key('italic_button'); final strikethroughKey = const Key('strikethrough_button'); @@ -45,7 +44,7 @@ void main() { home: Scaffold( body: MarkdownTextInput((String value) { initialValue = value; - }, initialValue)))); + }, initialValue: 'initial value')))); final formfield = tester.widget(find.text(initialValue)); formfield.controller.selection = TextSelection(baseOffset: 0, extentOffset: initialValue.length); @@ -62,7 +61,7 @@ void main() { home: Scaffold( body: MarkdownTextInput((String value) { initialValue = value; - }, initialValue)))); + }, initialValue: 'initial value')))); final formfield = tester.widget(find.text(initialValue)); formfield.controller.selection = TextSelection(baseOffset: 0, extentOffset: initialValue.length); @@ -79,7 +78,7 @@ void main() { home: Scaffold( body: MarkdownTextInput((String value) { initialValue = value; - }, initialValue)))); + }, initialValue: 'initial value')))); final formfield = tester.widget(find.text(initialValue)); formfield.controller.selection = TextSelection(baseOffset: 0, extentOffset: initialValue.length); @@ -96,7 +95,7 @@ void main() { home: Scaffold( body: MarkdownTextInput((String value) { initialValue = value; - }, initialValue)))); + }, initialValue: 'initial value')))); final formfield = tester.widget(find.text(initialValue)); formfield.controller.selection = TextSelection(baseOffset: 0, extentOffset: initialValue.length); @@ -113,7 +112,7 @@ void main() { home: Scaffold( body: MarkdownTextInput((String value) { initialValue = value; - }, initialValue)))); + }, initialValue: 'initial value')))); final formfield = tester.widget(find.text(initialValue)); formfield.controller.selection = TextSelection(baseOffset: 0, extentOffset: initialValue.length); @@ -130,7 +129,7 @@ void main() { home: Scaffold( body: MarkdownTextInput((String value) { initialValue = value; - }, initialValue)))); + }, initialValue: 'initial value')))); final formfield = tester.widget(find.text(initialValue)); formfield.controller.selection = TextSelection(baseOffset: 0, extentOffset: initialValue.length); @@ -147,7 +146,7 @@ void main() { home: Scaffold( body: MarkdownTextInput((String value) { initialValue = value; - }, initialValue)))); + }, initialValue: 'initial value')))); final formfield = tester.widget(find.text(initialValue)); formfield.controller.selection = TextSelection(baseOffset: 0, extentOffset: initialValue.length); @@ -164,7 +163,7 @@ void main() { home: Scaffold( body: MarkdownTextInput((String value) { initialValue = value; - }, initialValue)))); + }, initialValue: 'initial value')))); final formfield = tester.widget(find.text(initialValue)); formfield.controller.selection = TextSelection(baseOffset: 0, extentOffset: initialValue.length); @@ -181,7 +180,7 @@ void main() { home: Scaffold( body: MarkdownTextInput((String value) { initialValue = value; - }, initialValue)))); + }, initialValue: 'initial value')))); final formfield = tester.widget(find.text(initialValue)); formfield.controller.selection = TextSelection(baseOffset: 0, extentOffset: initialValue.length);