From f520450d6ce4557ae5512293a3f73ed642bc5e62 Mon Sep 17 00:00:00 2001 From: Bastien Date: Fri, 24 Mar 2023 10:46:19 +0100 Subject: [PATCH 01/10] :sparkles: (additionalButton) : Add possiblity to add custom buttons --- example/pubspec.lock | 37 ++++++++++++++++++------------- lib/format_markdown.dart | 10 +++++++++ lib/markdown_text_input.dart | 18 +++++++++++++--- pubspec.lock | 42 ++++++++++++++++++------------------ 4 files changed, 68 insertions(+), 39 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 79c7c48..3076ba0 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.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -42,14 +42,14 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: 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.1" flutter: dependency: "direct main" description: flutter @@ -122,28 +122,35 @@ packages: path: ".." relative: true source: path - version: "3.0.0" + version: "2.2.0" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -155,7 +162,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -176,21 +183,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" + version: "0.4.12" translator: dependency: transitive description: @@ -211,7 +218,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.0.0" diff --git a/lib/format_markdown.dart b/lib/format_markdown.dart index 72c9403..8c3f44d 100644 --- a/lib/format_markdown.dart +++ b/lib/format_markdown.dart @@ -132,6 +132,16 @@ enum MarkdownType { image, } +class ActionButton { + final Widget widget; + final String title; + final Function() action; + final List children; + final Key key; + + ActionButton(this.key, {required this.widget, required this.action, this.children = const [], this.title = ""}); +} + /// Add data to [MarkdownType] enum extension MarkownTypeExtension on MarkdownType { /// Get String used in widget's key diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index 1690566..34ede96 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -30,6 +30,8 @@ class MarkdownTextInput extends StatefulWidget { /// List of action the component can handle final List actions; + final List optionnalActionButtons; + /// Optional controller to manage the input final TextEditingController? controller; @@ -58,6 +60,7 @@ class MarkdownTextInput extends StatefulWidget { this.textStyle, this.controller, this.insertLinksByDialog = true, + this.optionnalActionButtons = const [] }); @override @@ -144,7 +147,9 @@ class _MarkdownTextInputState extends State { borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(10), bottomRight: Radius.circular(10)), child: ListView( scrollDirection: Axis.horizontal, - children: widget.actions.map((type) { + children: [ + + ...widget.actions.map((type) { switch (type) { case MarkdownType.title: return ExpandableNotifier( @@ -289,10 +294,17 @@ class _MarkdownTextInputState extends State { return _basicInkwell(type); } }).toList(), - ), + + ...widget.optionnalActionButtons.map((ActionButton optionActionButton) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell(child: optionActionButton.widget, onTap: optionActionButton.action), + ); + }).toList() + ] ), ) - ], + )], ), ); } diff --git a/pubspec.lock b/pubspec.lock index cef1c63..dd14740 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -21,28 +21,21 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: 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 +56,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -94,21 +87,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -120,7 +120,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -141,21 +141,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" + version: "0.4.12" translator: dependency: "direct main" description: @@ -176,7 +176,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.0.0" From 6a49e0fb1ab26e486d15781a8fb6b9fa85bc68e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9mence=20Roumy?= Date: Fri, 24 Mar 2023 11:08:57 +0100 Subject: [PATCH 02/10] remove google dependency + WIP: input decoration as parameters on dialog --- CHANGELOG.md | 4 ++ .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/pubspec.lock | 23 +----- lib/markdown_text_input.dart | 71 ++++++------------- pubspec.lock | 21 ------ pubspec.yaml | 2 - 6 files changed, 26 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de0c0b8..9718136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ A dialog will show where you can enter the link. * Add textStyle property +## [2.0.5] - 2023/03/24 + +* Remove Google dependency + ## [2.0.4] - 2022/10/28 * Do not dispose controller when it is passed as an argument diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 296b146..da44bc1 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip diff --git a/example/pubspec.lock b/example/pubspec.lock index 79c7c48..4200b3b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -95,20 +95,6 @@ packages: description: flutter source: sdk version: "0.0.0" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.5" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.2" markdown: dependency: transitive description: @@ -122,7 +108,7 @@ packages: path: ".." relative: true source: path - version: "3.0.0" + version: "2.2.0" matcher: dependency: transitive description: @@ -191,13 +177,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.3" - translator: - dependency: transitive - description: - name: translator - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.7" typed_data: dependency: transitive description: diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index 1690566..44287cb 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -5,7 +5,6 @@ import 'package:expandable/expandable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:markdown_editable_textinput/format_markdown.dart'; -import 'package:translator/translator.dart'; /// Widget with markdown buttons class MarkdownTextInput extends StatefulWidget { @@ -40,6 +39,12 @@ class MarkdownTextInput extends StatefulWidget { /// Default value is true. final bool insertLinksByDialog; + /// Label for the text input of the link dialog + final InputDecoration dialogTextDecoration; + + /// Label for the link input of the link dialog + final String dialogLinkLabel; + /// Constructor for [MarkdownTextInput] MarkdownTextInput( this.onTextChanged, @@ -48,16 +53,12 @@ class MarkdownTextInput extends StatefulWidget { this.validators, this.textDirection = TextDirection.ltr, this.maxLines = 10, - this.actions = const [ - MarkdownType.bold, - MarkdownType.italic, - MarkdownType.title, - MarkdownType.link, - MarkdownType.list - ], + this.actions = const [MarkdownType.bold, MarkdownType.italic, MarkdownType.title, MarkdownType.link, MarkdownType.list], this.textStyle, this.controller, this.insertLinksByDialog = true, + this.dialogTextDecoration = const InputDecoration(labelText: 'Text'), + this.dialogLinkLabel = 'Link', }); @override @@ -78,11 +79,10 @@ class _MarkdownTextInputState extends State { var fromIndex = textSelection.baseOffset; var toIndex = textSelection.extentOffset; - final result = FormatMarkdown.convertToMarkdown(type, _controller.text, fromIndex, toIndex, - titleSize: titleSize, link: link, selectedText: selectedText ?? _controller.text.substring(fromIndex, toIndex)); + final result = + FormatMarkdown.convertToMarkdown(type, _controller.text, fromIndex, toIndex, titleSize: titleSize, link: link, selectedText: selectedText ?? _controller.text.substring(fromIndex, toIndex)); - _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); @@ -128,10 +128,8 @@ class _MarkdownTextInputState extends State { 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)), + 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), @@ -196,8 +194,7 @@ class _MarkdownTextInputState extends State { customOnTap: !widget.insertLinksByDialog ? null : () async { - var text = - _controller.text.substring(textSelection.baseOffset, textSelection.extentOffset); + var text = _controller.text.substring(textSelection.baseOffset, textSelection.extentOffset); var textController = TextEditingController()..text = text; var linkController = TextEditingController(); @@ -205,21 +202,6 @@ class _MarkdownTextInputState extends State { var linkFocus = FocusNode(); var color = Theme.of(context).colorScheme.secondary; - var language = - kIsWeb ? window.locale.languageCode : Platform.localeName.substring(0, 2); - - var textLabel = 'Text'; - var linkLabel = 'Link'; - try { - var textTranslation = await GoogleTranslator().translate(textLabel, to: language); - textLabel = textTranslation.text; - - var linkTranslation = await GoogleTranslator().translate(linkLabel, to: language); - linkLabel = linkTranslation.text; - } catch (e) { - textLabel = 'Text'; - linkLabel = 'Link'; - } await showDialog( context: context, @@ -227,25 +209,14 @@ class _MarkdownTextInputState extends State { return AlertDialog( title: Row( mainAxisAlignment: MainAxisAlignment.end, - children: [ - GestureDetector( - child: Icon(Icons.close), onTap: () => Navigator.pop(context)) - ], + children: [GestureDetector(child: Icon(Icons.close), onTap: () => Navigator.pop(context))], ), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: textController, - decoration: InputDecoration( - hintText: 'example', - label: Text(textLabel), - labelStyle: TextStyle(color: color), - focusedBorder: - OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), - enabledBorder: - OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), - ), + decoration: widget.dialogTextDecoration, autofocus: text.isEmpty, focusNode: textFocus, textInputAction: TextInputAction.next, @@ -259,12 +230,10 @@ class _MarkdownTextInputState extends State { controller: linkController, decoration: InputDecoration( hintText: 'https://example.com', - label: Text(linkLabel), + label: Text(widget.dialogLinkLabel), labelStyle: TextStyle(color: color), - focusedBorder: - OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), - enabledBorder: - OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), + focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), ), autofocus: text.isNotEmpty, focusNode: linkFocus, diff --git a/pubspec.lock b/pubspec.lock index cef1c63..c925182 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -74,20 +74,6 @@ packages: description: flutter source: sdk version: "0.0.0" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.5" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.2" matcher: dependency: transitive description: @@ -156,13 +142,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.3" - translator: - dependency: "direct main" - description: - name: translator - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.7" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7dd57c6..a4a51a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,8 +12,6 @@ dependencies: sdk: flutter effective_dart: ^1.3.2 expandable: ^5.0.1 - translator: ^0.1.7 - dev_dependencies: flutter_test: From 717263e4174374eebe3f34514d387a1a352a5b2a Mon Sep 17 00:00:00 2001 From: Bastien Date: Fri, 24 Mar 2023 11:34:23 +0100 Subject: [PATCH 03/10] :recycle: (additionalButton) : Use _basicInkWell method to build button --- lib/markdown_text_input.dart | 38 ++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index 34ede96..f7bb801 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -296,10 +296,7 @@ class _MarkdownTextInputState extends State { }).toList(), ...widget.optionnalActionButtons.map((ActionButton optionActionButton) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: InkWell(child: optionActionButton.widget, onTap: optionActionButton.action), - ); + return _basicInkwell(optionActionButton, customOnTap: optionActionButton.action); }).toList() ] ), @@ -309,14 +306,29 @@ class _MarkdownTextInputState extends State { ); } - Widget _basicInkwell(MarkdownType type, {Function? customOnTap}) { - return InkWell( - key: Key(type.key), - onTap: () => customOnTap != null ? customOnTap() : onTap(type), - child: Padding( - padding: EdgeInsets.all(10), - child: Icon(type.icon), - ), - ); + Widget _basicInkwell(dynamic item, {Function? customOnTap}) { + Widget widgetToReturn = SizedBox.shrink(); + + if (item is MarkdownType) { + return InkWell( + key: Key(item.key), + onTap: () => customOnTap != null ? customOnTap() : onTap(item), + child: Padding( + padding: EdgeInsets.all(10), + child: Icon(item.icon), + ), + ); + } else if (item is ActionButton) { + return InkWell( + key: item.key, + onTap: item.action, + child: Padding( + padding: EdgeInsets.all(10), + child: item.widget, + ), + ); + } + + return widgetToReturn; } } From de005e49fb6875f1a83e35498465dada44042a58 Mon Sep 17 00:00:00 2001 From: Bastien Date: Fri, 24 Mar 2023 11:54:41 +0100 Subject: [PATCH 04/10] :lipstick: (linkDialog) : Add TextInputDecoration parameter for link Dialog --- lib/markdown_text_input.dart | 38 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index 44287cb..4189582 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -39,11 +39,11 @@ class MarkdownTextInput extends StatefulWidget { /// Default value is true. final bool insertLinksByDialog; - /// Label for the text input of the link dialog - final InputDecoration dialogTextDecoration; + /// InputDecoration for the text input of the link dialog + final InputDecoration? linkDialogLinkDecoration; - /// Label for the link input of the link dialog - final String dialogLinkLabel; + /// InputDecoration for the link input of the link dialog + final InputDecoration? linkDialogTextDecoration; /// Constructor for [MarkdownTextInput] MarkdownTextInput( @@ -57,8 +57,8 @@ class MarkdownTextInput extends StatefulWidget { this.textStyle, this.controller, this.insertLinksByDialog = true, - this.dialogTextDecoration = const InputDecoration(labelText: 'Text'), - this.dialogLinkLabel = 'Link', + this.linkDialogLinkDecoration, + this.linkDialogTextDecoration, }); @override @@ -207,16 +207,20 @@ class _MarkdownTextInputState extends State { context: context, builder: (context) { return AlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [GestureDetector(child: Icon(Icons.close), onTap: () => Navigator.pop(context))], - ), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: textController, - decoration: widget.dialogTextDecoration, + decoration: widget.linkDialogLinkDecoration ?? InputDecoration( + hintText: 'Example text', + label: Text('Text'), + labelStyle: TextStyle(color: color), + focusedBorder: + OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), + enabledBorder: + OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), + ), autofocus: text.isEmpty, focusNode: textFocus, textInputAction: TextInputAction.next, @@ -228,9 +232,9 @@ class _MarkdownTextInputState extends State { SizedBox(height: 10), TextField( controller: linkController, - decoration: InputDecoration( + decoration: widget.linkDialogTextDecoration ?? InputDecoration( hintText: 'https://example.com', - label: Text(widget.dialogLinkLabel), + label: Text("Link"), labelStyle: TextStyle(color: color), focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), @@ -240,8 +244,14 @@ class _MarkdownTextInputState extends State { ), ], ), - contentPadding: EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 0), + contentPadding: EdgeInsets.fromLTRB(24.0, 32.0, 24.0, 12.0), actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Annuler'), + ), TextButton( onPressed: () { onTap(type, link: linkController.text, selectedText: textController.text); From b7a5a8e01341c3a96ca5298d0dd13e3b89667928 Mon Sep 17 00:00:00 2001 From: Bastien Date: Fri, 24 Mar 2023 12:01:57 +0100 Subject: [PATCH 05/10] :recycle: (dialog) : Make Dialog external component --- lib/markdown_text_input.dart | 220 ++++++++++++++++++++++------------- 1 file changed, 138 insertions(+), 82 deletions(-) diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index 4189582..41bbf68 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -53,7 +53,13 @@ class MarkdownTextInput extends StatefulWidget { this.validators, this.textDirection = TextDirection.ltr, this.maxLines = 10, - this.actions = const [MarkdownType.bold, MarkdownType.italic, MarkdownType.title, MarkdownType.link, MarkdownType.list], + this.actions = const [ + MarkdownType.bold, + MarkdownType.italic, + MarkdownType.title, + MarkdownType.link, + MarkdownType.list + ], this.textStyle, this.controller, this.insertLinksByDialog = true, @@ -62,30 +68,42 @@ class MarkdownTextInput extends StatefulWidget { }); @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); FocusNode focusNode = FocusNode(); _MarkdownTextInputState(this._controller); - void onTap(MarkdownType type, {int titleSize = 1, String? link, String? selectedText}) { + void onTap(MarkdownType type, + {int titleSize = 1, String? link, String? selectedText}) { final basePosition = textSelection.baseOffset; - var noTextSelected = (textSelection.baseOffset - textSelection.extentOffset) == 0; + var noTextSelected = + (textSelection.baseOffset - textSelection.extentOffset) == 0; var fromIndex = textSelection.baseOffset; var toIndex = textSelection.extentOffset; - final result = - FormatMarkdown.convertToMarkdown(type, _controller.text, fromIndex, toIndex, titleSize: titleSize, link: link, selectedText: selectedText ?? _controller.text.substring(fromIndex, toIndex)); + final result = FormatMarkdown.convertToMarkdown( + type, _controller.text, fromIndex, toIndex, + titleSize: titleSize, + link: link, + selectedText: + selectedText ?? _controller.text.substring(fromIndex, toIndex)); - _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); focusNode.requestFocus(); } } @@ -94,7 +112,8 @@ class _MarkdownTextInputState extends State { void initState() { _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(); @@ -112,7 +131,8 @@ class _MarkdownTextInputState extends State { return Container( decoration: BoxDecoration( color: Theme.of(context).cardColor, - border: Border.all(color: Theme.of(context).colorScheme.secondary, width: 2), + border: Border.all( + color: Theme.of(context).colorScheme.secondary, width: 2), borderRadius: const BorderRadius.all(Radius.circular(10)), ), child: Column( @@ -123,23 +143,33 @@ class _MarkdownTextInputState extends State { maxLines: widget.maxLines, controller: _controller, textCapitalization: TextCapitalization.sentences, - validator: widget.validators != null ? (value) => widget.validators!(value) : null, + validator: widget.validators != null + ? (value) => widget.validators!(value) + : null, style: widget.textStyle ?? Theme.of(context).textTheme.bodyText1, 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)), + 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), + 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)), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10)), child: ListView( scrollDirection: Axis.horizontal, children: widget.actions.map((type) { @@ -154,7 +184,9 @@ 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), ), ), ), @@ -166,12 +198,15 @@ class _MarkdownTextInputState extends State { for (int i = 1; i <= 6; i++) InkWell( key: Key('H${i}_button'), - onTap: () => onTap(MarkdownType.title, titleSize: i), + 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), + style: TextStyle( + fontSize: (18 - i).toDouble(), + fontWeight: FontWeight.w700), ), ), ), @@ -194,74 +229,20 @@ class _MarkdownTextInputState extends State { customOnTap: !widget.insertLinksByDialog ? null : () async { - var text = _controller.text.substring(textSelection.baseOffset, textSelection.extentOffset); + var text = _controller.text.substring( + textSelection.baseOffset, + textSelection.extentOffset); - var textController = TextEditingController()..text = text; + var textController = TextEditingController() + ..text = text; var linkController = TextEditingController(); var textFocus = FocusNode(); var linkFocus = FocusNode(); - var color = Theme.of(context).colorScheme.secondary; + var color = + Theme.of(context).colorScheme.secondary; - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: textController, - decoration: widget.linkDialogLinkDecoration ?? InputDecoration( - hintText: 'Example text', - label: Text('Text'), - labelStyle: TextStyle(color: color), - focusedBorder: - OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), - enabledBorder: - OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), - ), - autofocus: text.isEmpty, - focusNode: textFocus, - textInputAction: TextInputAction.next, - onSubmitted: (value) { - textFocus.unfocus(); - FocusScope.of(context).requestFocus(linkFocus); - }, - ), - SizedBox(height: 10), - TextField( - controller: linkController, - decoration: widget.linkDialogTextDecoration ?? InputDecoration( - hintText: 'https://example.com', - label: Text("Link"), - labelStyle: TextStyle(color: color), - focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), - enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), - ), - autofocus: text.isNotEmpty, - focusNode: linkFocus, - ), - ], - ), - contentPadding: EdgeInsets.fromLTRB(24.0, 32.0, 24.0, 12.0), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Annuler'), - ), - TextButton( - onPressed: () { - onTap(type, link: linkController.text, selectedText: textController.text); - Navigator.pop(context); - }, - child: const Text('OK'), - ), - ], - ); - }); + await _basicDialog(textController, linkController, color, text, textFocus, linkFocus, type); }, ); default: @@ -286,4 +267,79 @@ class _MarkdownTextInputState extends State { ), ); } + + Future _basicDialog( + TextEditingController textController, + TextEditingController linkController, + Color color, + String text, + FocusNode textFocus, + FocusNode linkFocus, + MarkdownType type) async { + return await showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: textController, + decoration: widget.linkDialogLinkDecoration ?? + InputDecoration( + hintText: 'Example text', + label: Text('Text'), + labelStyle: TextStyle(color: color), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: color, width: 2)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: color, width: 2)), + ), + autofocus: text.isEmpty, + focusNode: textFocus, + textInputAction: TextInputAction.next, + onSubmitted: (value) { + textFocus.unfocus(); + FocusScope.of(context).requestFocus(linkFocus); + }, + ), + SizedBox(height: 10), + TextField( + controller: linkController, + decoration: widget.linkDialogTextDecoration ?? + InputDecoration( + hintText: 'https://example.com', + label: Text("Link"), + labelStyle: TextStyle(color: color), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: color, width: 2)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: color, width: 2)), + ), + autofocus: text.isNotEmpty, + focusNode: linkFocus, + ), + ], + ), + contentPadding: EdgeInsets.fromLTRB(24.0, 32.0, 24.0, 12.0), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Annuler'), + ), + TextButton( + onPressed: () { + onTap(type, + link: linkController.text, + selectedText: textController.text); + Navigator.pop(context); + }, + child: const Text('OK'), + ), + ], + ); + }); + } } From 8cd7aefe66deb76c8fbc790dbf96bcbac592c186 Mon Sep 17 00:00:00 2001 From: Bastien Date: Fri, 24 Mar 2023 15:01:10 +0100 Subject: [PATCH 06/10] :sparkles: (dialog) : Add dialog possibility to image --- example/pubspec.lock | 42 ++++++++++++------------- lib/format_markdown.dart | 2 +- lib/markdown_text_input.dart | 59 ++++++++++++++++++++++++++++++++---- pubspec.lock | 49 +++++++++++++----------------- 4 files changed, 96 insertions(+), 56 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 4200b3b..a351089 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.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -42,14 +42,14 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: 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.1" flutter: dependency: "direct main" description: flutter @@ -115,21 +115,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" 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.9.0" stack_trace: dependency: transitive description: @@ -162,35 +169,28 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "0.4.12" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.0.0" diff --git a/lib/format_markdown.dart b/lib/format_markdown.dart index 72c9403..c1c1765 100644 --- a/lib/format_markdown.dart +++ b/lib/format_markdown.dart @@ -66,7 +66,7 @@ class FormatMarkdown { replaceCursorIndex = 0; break; case MarkdownType.image: - changedData = '![${data.substring(fromIndex, toIndex)}](${data.substring(fromIndex, toIndex)})'; + changedData = '![$selectedText](${link ?? selectedText})'; replaceCursorIndex = 3; break; } diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index 41bbf68..7cf0154 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -39,12 +39,28 @@ class MarkdownTextInput extends StatefulWidget { /// Default value is true. final bool insertLinksByDialog; + /// If you prefer to use the dialog to insert image, you can choose to use the markdown syntax directly by setting [insertImageByDialog] to false. In this case, the selected text will be used as label and link. + /// Default value is true. + final bool insertImageByDialog; + /// InputDecoration for the text input of the link dialog final InputDecoration? linkDialogLinkDecoration; /// InputDecoration for the link input of the link dialog final InputDecoration? linkDialogTextDecoration; + /// InputDecoration for the text input of the image dialog + final InputDecoration? imageDialogLinkDecoration; + + /// InputDecoration for the link input of the image dialog + final InputDecoration? imageDialogTextDecoration; + + /// Custom text for cancel button in dialogs + final String? customCancelDialogText; + + /// Custom text for submit button in dialogs + final String? customSubmitDialogText; + /// Constructor for [MarkdownTextInput] MarkdownTextInput( this.onTextChanged, @@ -63,8 +79,13 @@ class MarkdownTextInput extends StatefulWidget { this.textStyle, this.controller, this.insertLinksByDialog = true, + this.insertImageByDialog = true, this.linkDialogLinkDecoration, this.linkDialogTextDecoration, + this.imageDialogLinkDecoration, + this.imageDialogTextDecoration, + this.customCancelDialogText, + this.customSubmitDialogText }); @override @@ -245,6 +266,28 @@ class _MarkdownTextInputState extends State { await _basicDialog(textController, linkController, color, text, textFocus, linkFocus, type); }, ); + case MarkdownType.image: + return _basicInkwell( + type, + customOnTap: !widget.insertImageByDialog + ? null + : () async { + var text = _controller.text.substring( + textSelection.baseOffset, + textSelection.extentOffset); + + var textController = TextEditingController() + ..text = text; + var linkController = TextEditingController(); + var textFocus = FocusNode(); + var linkFocus = FocusNode(); + + var color = + Theme.of(context).colorScheme.secondary; + + await _basicDialog(textController, linkController, color, text, textFocus, linkFocus, type); + }, + ); default: return _basicInkwell(type); } @@ -275,7 +318,11 @@ class _MarkdownTextInputState extends State { String text, FocusNode textFocus, FocusNode linkFocus, - MarkdownType type) async { + MarkdownType type, + ) async { + var finalTextInputDecoration = type == MarkdownType.link ? widget.linkDialogTextDecoration : widget.imageDialogTextDecoration; + var finalLinkInputDecoration = type == MarkdownType.link ? widget.linkDialogLinkDecoration : widget.imageDialogLinkDecoration; + return await showDialog( context: context, builder: (context) { @@ -285,7 +332,7 @@ class _MarkdownTextInputState extends State { children: [ TextField( controller: textController, - decoration: widget.linkDialogLinkDecoration ?? + decoration: finalTextInputDecoration ?? InputDecoration( hintText: 'Example text', label: Text('Text'), @@ -306,10 +353,10 @@ class _MarkdownTextInputState extends State { SizedBox(height: 10), TextField( controller: linkController, - decoration: widget.linkDialogTextDecoration ?? + decoration: finalLinkInputDecoration ?? InputDecoration( hintText: 'https://example.com', - label: Text("Link"), + label: Text('Link'), labelStyle: TextStyle(color: color), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: color, width: 2)), @@ -327,7 +374,7 @@ class _MarkdownTextInputState extends State { onPressed: () { Navigator.pop(context); }, - child: const Text('Annuler'), + child: Text(widget.customCancelDialogText ?? 'Annuler'), ), TextButton( onPressed: () { @@ -336,7 +383,7 @@ class _MarkdownTextInputState extends State { selectedText: textController.text); Navigator.pop(context); }, - child: const Text('OK'), + child: Text(widget.customSubmitDialogText ??'OK'), ), ], ); diff --git a/pubspec.lock b/pubspec.lock index c925182..e6d025f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -21,28 +21,21 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: 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 +56,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -80,21 +73,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -106,7 +106,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -127,35 +127,28 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "0.4.12" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.0.0" From e761ee528c8e3e92ad3d93fb4253a00d18be802e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9mence=20Roumy?= Date: Fri, 24 Mar 2023 15:50:33 +0100 Subject: [PATCH 07/10] change version + refactor --- CHANGELOG.md | 10 +- example/pubspec.lock | 44 +++--- lib/markdown_text_input.dart | 275 +++++++++++++---------------------- pubspec.lock | 49 ++++--- pubspec.yaml | 2 +- 5 files changed, 159 insertions(+), 221 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9718136..d0910db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [2.3.0] - 2023/03/24 + +* Add support for custom action buttons +* Add dialog mode on pictures +* Remove Google dependency + ## [2.2.0] - 2023/01/02 * Change the way of adding links to the input. Now, you need to select the text you want to be the label, then click the link button. @@ -7,10 +13,6 @@ A dialog will show where you can enter the link. * Add textStyle property -## [2.0.5] - 2023/03/24 - -* Remove Google dependency - ## [2.0.4] - 2022/10/28 * Do not dispose controller when it is passed as an argument diff --git a/example/pubspec.lock b/example/pubspec.lock index a351089..e790cc5 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.9.0" + 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.2.1" + version: "1.2.0" charcode: dependency: transitive description: @@ -42,14 +42,14 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.16.0" + version: "1.15.0" cupertino_icons: dependency: "direct main" description: @@ -77,7 +77,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -108,35 +108,28 @@ packages: path: ".." relative: true source: path - version: "2.2.0" + version: "2.3.0" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.12" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.5" + version: "0.12.11" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.7.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.8.0" sky_engine: dependency: transitive description: flutter @@ -148,7 +141,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.9.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -169,28 +162,35 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.12" + version: "0.4.3" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.1" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.14.0 <3.0.0" flutter: ">=2.0.0" diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index 7cf0154..c2c935a 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -62,69 +62,48 @@ class MarkdownTextInput extends StatefulWidget { final String? customSubmitDialogText; /// Constructor for [MarkdownTextInput] - MarkdownTextInput( - this.onTextChanged, - this.initialValue, { - this.label = '', - this.validators, - this.textDirection = TextDirection.ltr, - this.maxLines = 10, - this.actions = const [ - MarkdownType.bold, - MarkdownType.italic, - MarkdownType.title, - MarkdownType.link, - MarkdownType.list - ], - this.textStyle, - this.controller, - this.insertLinksByDialog = true, - this.insertImageByDialog = true, - this.linkDialogLinkDecoration, - this.linkDialogTextDecoration, - this.imageDialogLinkDecoration, - this.imageDialogTextDecoration, - this.customCancelDialogText, - this.customSubmitDialogText - }); + MarkdownTextInput(this.onTextChanged, this.initialValue, + {this.label = '', + this.validators, + this.textDirection = TextDirection.ltr, + this.maxLines = 10, + this.actions = const [MarkdownType.bold, MarkdownType.italic, MarkdownType.title, MarkdownType.link, MarkdownType.list], + this.textStyle, + this.controller, + this.insertLinksByDialog = true, + this.insertImageByDialog = true, + this.linkDialogLinkDecoration, + this.linkDialogTextDecoration, + this.imageDialogLinkDecoration, + this.imageDialogTextDecoration, + this.customCancelDialogText, + this.customSubmitDialogText}); @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); FocusNode focusNode = FocusNode(); _MarkdownTextInputState(this._controller); - void onTap(MarkdownType type, - {int titleSize = 1, String? link, String? selectedText}) { + void onTap(MarkdownType type, {int titleSize = 1, String? link, String? selectedText}) { final basePosition = textSelection.baseOffset; - var noTextSelected = - (textSelection.baseOffset - textSelection.extentOffset) == 0; + var noTextSelected = (textSelection.baseOffset - textSelection.extentOffset) == 0; var fromIndex = textSelection.baseOffset; var toIndex = textSelection.extentOffset; - final result = FormatMarkdown.convertToMarkdown( - type, _controller.text, fromIndex, toIndex, - titleSize: titleSize, - link: link, - selectedText: - selectedText ?? _controller.text.substring(fromIndex, toIndex)); + final result = + FormatMarkdown.convertToMarkdown(type, _controller.text, fromIndex, toIndex, titleSize: titleSize, link: link, selectedText: selectedText ?? _controller.text.substring(fromIndex, toIndex)); - _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); focusNode.requestFocus(); } } @@ -133,8 +112,7 @@ class _MarkdownTextInputState extends State { void initState() { _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(); @@ -152,8 +130,7 @@ class _MarkdownTextInputState extends State { return Container( decoration: BoxDecoration( color: Theme.of(context).cardColor, - border: Border.all( - color: Theme.of(context).colorScheme.secondary, width: 2), + border: Border.all(color: Theme.of(context).colorScheme.secondary, width: 2), borderRadius: const BorderRadius.all(Radius.circular(10)), ), child: Column( @@ -164,132 +141,89 @@ class _MarkdownTextInputState extends State { maxLines: widget.maxLines, controller: _controller, textCapitalization: TextCapitalization.sentences, - validator: widget.validators != null - ? (value) => widget.validators!(value) - : null, + validator: widget.validators != null ? (value) => widget.validators!(value) : null, style: widget.textStyle ?? Theme.of(context).textTheme.bodyText1, 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)), + 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), + 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)), + borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(10), bottomRight: Radius.circular(10)), child: ListView( scrollDirection: Axis.horizontal, children: widget.actions.map((type) { - switch (type) { - case MarkdownType.title: - return ExpandableNotifier( - child: Expandable( - key: Key('H#_button'), - collapsed: ExpandableButton( - child: const Center( - child: Padding( - padding: EdgeInsets.all(10), - child: Text( - 'H#', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700), - ), + if (type == MarkdownType.title) { + return ExpandableNotifier( + child: Expandable( + key: Key('H#_button'), + collapsed: ExpandableButton( + child: const Center( + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + 'H#', + 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), - ), + ), + 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), ), ), - ExpandableButton( - child: const Padding( - padding: EdgeInsets.all(10), - child: Icon( - Icons.close, - ), + ), + ExpandableButton( + child: const Padding( + padding: EdgeInsets.all(10), + child: Icon( + Icons.close, ), ), - ], - ), + ), + ], ), ), - ); - case MarkdownType.link: - return _basicInkwell( - type, - customOnTap: !widget.insertLinksByDialog - ? null - : () async { - var text = _controller.text.substring( - textSelection.baseOffset, - textSelection.extentOffset); - - var textController = TextEditingController() - ..text = text; - var linkController = TextEditingController(); - var textFocus = FocusNode(); - var linkFocus = FocusNode(); - - var color = - Theme.of(context).colorScheme.secondary; - - await _basicDialog(textController, linkController, color, text, textFocus, linkFocus, type); - }, - ); - case MarkdownType.image: - return _basicInkwell( - type, - customOnTap: !widget.insertImageByDialog - ? null - : () async { - var text = _controller.text.substring( - textSelection.baseOffset, - textSelection.extentOffset); - - var textController = TextEditingController() - ..text = text; - var linkController = TextEditingController(); - var textFocus = FocusNode(); - var linkFocus = FocusNode(); - - var color = - Theme.of(context).colorScheme.secondary; - - await _basicDialog(textController, linkController, color, text, textFocus, linkFocus, type); - }, - ); - default: - return _basicInkwell(type); + ), + ); + } + else if (type == MarkdownType.link || type == MarkdownType.image) { + return _basicInkwell( + type, + customOnTap: (type == MarkdownType.link ? !widget.insertLinksByDialog : !widget.insertImageByDialog) + ? null + : () async { + var text = _controller.text.substring(textSelection.baseOffset, textSelection.extentOffset); + + var textController = TextEditingController()..text = text; + var linkController = TextEditingController(); + + var color = Theme.of(context).colorScheme.secondary; + + await _basicDialog(textController, linkController, color, text, type); + }, + ); + } else { + return _basicInkwell(type); } }).toList(), ), @@ -312,17 +246,18 @@ class _MarkdownTextInputState extends State { } Future _basicDialog( - TextEditingController textController, - TextEditingController linkController, - Color color, - String text, - FocusNode textFocus, - FocusNode linkFocus, - MarkdownType type, - ) async { + TextEditingController textController, + TextEditingController linkController, + Color color, + String text, + MarkdownType type, + ) async { var finalTextInputDecoration = type == MarkdownType.link ? widget.linkDialogTextDecoration : widget.imageDialogTextDecoration; var finalLinkInputDecoration = type == MarkdownType.link ? widget.linkDialogLinkDecoration : widget.imageDialogLinkDecoration; + var textFocus = FocusNode(); + var linkFocus = FocusNode(); + return await showDialog( context: context, builder: (context) { @@ -337,10 +272,8 @@ class _MarkdownTextInputState extends State { hintText: 'Example text', label: Text('Text'), labelStyle: TextStyle(color: color), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: color, width: 2)), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: color, width: 2)), + focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), ), autofocus: text.isEmpty, focusNode: textFocus, @@ -358,10 +291,8 @@ class _MarkdownTextInputState extends State { hintText: 'https://example.com', label: Text('Link'), labelStyle: TextStyle(color: color), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: color, width: 2)), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: color, width: 2)), + focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: color, width: 2)), ), autofocus: text.isNotEmpty, focusNode: linkFocus, @@ -374,16 +305,14 @@ class _MarkdownTextInputState extends State { onPressed: () { Navigator.pop(context); }, - child: Text(widget.customCancelDialogText ?? 'Annuler'), + child: Text(widget.customCancelDialogText ?? 'Cancel'), ), TextButton( onPressed: () { - onTap(type, - link: linkController.text, - selectedText: textController.text); + onTap(type, link: linkController.text, selectedText: textController.text); Navigator.pop(context); }, - child: Text(widget.customSubmitDialogText ??'OK'), + child: Text(widget.customSubmitDialogText ?? 'OK'), ), ], ); diff --git a/pubspec.lock b/pubspec.lock index e6d025f..c925182 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.9.0" + version: "2.8.2" boolean_selector: dependency: transitive description: @@ -21,21 +21,28 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.16.0" + version: "1.15.0" effective_dart: dependency: "direct main" description: @@ -56,7 +63,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -73,28 +80,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.12" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.5" + version: "0.12.11" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.7.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.8.0" sky_engine: dependency: transitive description: flutter @@ -106,7 +106,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.9.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -127,28 +127,35 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.12" + version: "0.4.3" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.1" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.14.0 <3.0.0" flutter: ">=2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index a4a51a3..ec5204d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: markdown_editable_textinput description: A TextField Widget that allow you to convert easily what's in the TextField to Markdown. -version: 2.2.0 +version: 2.3.0 homepage: https://github.com/playmoweb/markdown-editable-textinput repository: https://github.com/playmoweb/markdown-editable-textinput From d3fca06e8d96ab84902f8a0ff94e199e2182769f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9mence=20Roumy?= Date: Fri, 24 Mar 2023 16:15:18 +0100 Subject: [PATCH 08/10] feat: custom btn in example app + refactor: remove useless properties on ActionButton + doc: document new properties/class --- README.md | 2 ++ example/lib/main.dart | 3 +++ lib/format_markdown.dart | 9 +++++---- lib/markdown_text_input.dart | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cc8da95..e0359b3 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ MarkdownEditableTextInput is a TextField Widget that allow you to convert easily - [x] Convert to Code, Quote, Links - [x] Convert to Heading (H1, H2, H3, H4, H5, H6) and Links - [x] Support text direction +- [x] Dialog mode to enter link and picture +- [x] Possibility to add custom buttons to the action bar ## Demo ![](pictures/test_edition.gif) diff --git a/example/lib/main.dart b/example/lib/main.dart index 675f09e..2bdb0bd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -57,6 +57,9 @@ class _MyAppState extends State { actions: MarkdownType.values, controller: controller, textStyle: TextStyle(fontSize: 16), + optionnalActionButtons: [ + ActionButton(widget: Icon(Icons.add), action: () => controller.text = '${controller.text} test ') + ], ), TextButton( onPressed: () { diff --git a/lib/format_markdown.dart b/lib/format_markdown.dart index e328965..55d4ad4 100644 --- a/lib/format_markdown.dart +++ b/lib/format_markdown.dart @@ -132,14 +132,15 @@ enum MarkdownType { image, } +/// Custom button object class ActionButton { + /// [widget] is the icon in the action bar final Widget widget; - final String title; + /// Action to perform when button is pressed final Function() action; - final List children; - final Key key; - ActionButton(this.key, {required this.widget, required this.action, this.children = const [], this.title = ""}); + /// return [ActionButton] + ActionButton({required this.widget, required this.action}); } /// Add data to [MarkdownType] enum diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index 97858a2..7a33e8b 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -29,6 +29,7 @@ class MarkdownTextInput extends StatefulWidget { /// List of action the component can handle final List actions; + /// List of custom action buttons final List optionnalActionButtons; /// Optional controller to manage the input @@ -257,7 +258,6 @@ class _MarkdownTextInputState extends State { ); } else if (item is ActionButton) { return InkWell( - key: item.key, onTap: item.action, child: Padding( padding: EdgeInsets.all(10), From fee3713ccf18bbd45a0274139462ce383bcb8b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9mence=20Roumy?= Date: Fri, 24 Mar 2023 16:20:16 +0100 Subject: [PATCH 09/10] add missing attributes to readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e0359b3..d31803f 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ The color of the MarkdownTextInput is defined by the color set in your Theme : | TextEditingController controller | TextEditingController() | Pass your own controller. Can be used to clear the input for example | | TextStyle textStyle | Theme.of(context).textTheme.bodyText2 | Overrides input text style | | bool insertLinksByDialog; | true | Choose to use dialog or not to insert link | +| bool insertImageByDialog; | true | Choose to use dialog or not to insert an image | +| bool insertImageByDialog; | true | Choose to use dialog or not to insert an image | +| bool customCancelDialogText; | String? | Text used by dialog for close dialog action | +| bool customSubmitDialogText; | String? | Text used by dialog for validate dialog action | ### Example You can see an example of how to use this package [here](https://github.com/playmoweb/markdown-editable-textinput/tree/master/example) From 583c002ff5a785d9c2f29fd4e89458373234066c Mon Sep 17 00:00:00 2001 From: Luis Pulido Date: Fri, 22 Sep 2023 14:11:38 -0400 Subject: [PATCH 10/10] Update markdown_text_input.dart --- lib/markdown_text_input.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/markdown_text_input.dart b/lib/markdown_text_input.dart index 1690566..7e61578 100644 --- a/lib/markdown_text_input.dart +++ b/lib/markdown_text_input.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:math'; import 'dart:ui'; import 'package:expandable/expandable.dart'; @@ -75,8 +76,8 @@ class _MarkdownTextInputState extends State { final basePosition = textSelection.baseOffset; var noTextSelected = (textSelection.baseOffset - textSelection.extentOffset) == 0; - var fromIndex = textSelection.baseOffset; - var toIndex = textSelection.extentOffset; + var fromIndex = min(textSelection.baseOffset, textSelection.extentOffset); + var toIndex = max(textSelection.extentOffset, textSelection.baseOffset); final result = FormatMarkdown.convertToMarkdown(type, _controller.text, fromIndex, toIndex, titleSize: titleSize, link: link, selectedText: selectedText ?? _controller.text.substring(fromIndex, toIndex));