diff --git a/example/lib/app.dart b/example/lib/app.dart index b6b2ad6..f952a08 100644 --- a/example/lib/app.dart +++ b/example/lib/app.dart @@ -14,34 +14,14 @@ class _AppState extends State { /// Initialize LanguageTool static final LanguageTool _languageTool = LanguageTool(); - /// Initialize DebounceLangToolService - static final DebounceLangToolService _debouncedLangService = - DebounceLangToolService( - LangToolService(_languageTool), - const Duration(milliseconds: 500), - ); - - /// Initialize ColoredTextEditingController - final ColoredTextEditingController _controller = - ColoredTextEditingController(languageCheckService: _debouncedLangService); - @override Widget build(BuildContext context) { return Material( child: LanguageToolTextField( style: const TextStyle(), decoration: const InputDecoration(), - mistakeBuilder: () { - return Container(); - }, - coloredController: _controller, + languageTool: _languageTool, ), ); } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } } diff --git a/lib/core/controllers/colored_text_editing_controller.dart b/lib/core/controllers/colored_text_editing_controller.dart index 3eb93cd..89c42be 100644 --- a/lib/core/controllers/colored_text_editing_controller.dart +++ b/lib/core/controllers/colored_text_editing_controller.dart @@ -1,8 +1,13 @@ +import 'dart:developer'; + +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:language_tool/language_tool.dart'; import 'package:languagetool_textfield/core/enums/mistake_type.dart'; import 'package:languagetool_textfield/domain/highlight_style.dart'; import 'package:languagetool_textfield/domain/language_check_service.dart'; import 'package:languagetool_textfield/domain/mistake.dart'; +import 'package:languagetool_textfield/implementations/lang_tool_service.dart'; /// A TextEditingController with overrides buildTextSpan for building /// marked TextSpans with tap recognizer @@ -13,6 +18,10 @@ class ColoredTextEditingController extends TextEditingController { /// Language tool API index final LanguageCheckService languageCheckService; + /// Register tap to show overlay popup + final Function(Offset globalPosition, Color color, Mistake mistake) + onMistakeTap; + /// List which contains Mistake objects spans are built from List _mistakes = []; @@ -24,8 +33,9 @@ class ColoredTextEditingController extends TextEditingController { /// Controller constructor ColoredTextEditingController({ + required this.onMistakeTap, required this.languageCheckService, - this.highlightStyle = const HighlightStyle(), + required this.highlightStyle, }); /// Clear mistakes list when text mas modified and get a new list of mistakes @@ -92,6 +102,16 @@ class ColoredTextEditingController extends TextEditingController { decorationColor: mistakeColor, decorationThickness: highlightStyle.mistakeLineThickness, ), + + /// Recognizes user tap on highlighted area + recognizer: TapGestureRecognizer() + ..onTapDown = (TapDownDetails details) { + _openPopup( + details: details, + color: mistakeColor, + mistake: mistake, + ); + }, ), ], ); @@ -106,6 +126,15 @@ class ColoredTextEditingController extends TextEditingController { ); } + void _openPopup({ + required TapDownDetails details, + required Color color, + required Mistake mistake, + }) { + log(details.globalPosition.toString()); + onMistakeTap(details.globalPosition, color, mistake); + } + /// Returns color for mistake TextSpan style Color _getMistakeColor(MistakeType type) { switch (type) { diff --git a/lib/languagetool_textfield.dart b/lib/languagetool_textfield.dart index 1050872..5ad5649 100644 --- a/lib/languagetool_textfield.dart +++ b/lib/languagetool_textfield.dart @@ -10,3 +10,4 @@ export 'implementations/debounce_lang_tool_service.dart'; export 'implementations/lang_tool_service.dart'; export 'implementations/throttling_lang_tool_service.dart'; export "presentation/language_tool_text_field.dart"; +export 'presentation/widgets/create_popup_entry.dart'; diff --git a/lib/presentation/language_tool_text_field.dart b/lib/presentation/language_tool_text_field.dart index b51523c..81ce791 100644 --- a/lib/presentation/language_tool_text_field.dart +++ b/lib/presentation/language_tool_text_field.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:languagetool_textfield/core/controllers/colored_text_editing_controller.dart'; +import 'package:languagetool_textfield/languagetool_textfield.dart'; /// A TextField widget that checks the grammar using the given -/// [coloredController] +/// [languageTool] class LanguageToolTextField extends StatefulWidget { /// A style to use for the text being edited. final TextStyle style; @@ -13,16 +13,20 @@ class LanguageToolTextField extends StatefulWidget { /// A builder function used to build errors. final Widget Function()? mistakeBuilder; - /// Color scheme to highlight mistakes - final ColoredTextEditingController coloredController; + /// A Service to get mistakes from API + final LanguageTool languageTool; + + /// User customization for mistake highlighting + final HighlightStyle mistakeHighlightStyle; /// Creates a widget that checks grammar errors. const LanguageToolTextField({ Key? key, required this.style, required this.decoration, + required this.languageTool, + this.mistakeHighlightStyle = const HighlightStyle(), this.mistakeBuilder, - required this.coloredController, }) : super(key: key); @override @@ -30,13 +34,46 @@ class LanguageToolTextField extends StatefulWidget { } class _LanguageToolTextFieldState extends State { + /// Initialize ColoredTextEditingController + ColoredTextEditingController? _controller; + + @override + void initState() { + /// Creating an instance of DebounceLangToolService + final DebounceLangToolService _debouncedLangService = + DebounceLangToolService( + LangToolService(widget.languageTool), + const Duration(milliseconds: 500), + ); + + /// Setting ColoredTextEditingController with DebounceLangToolService and + /// a Dialog trigger function + _controller = ColoredTextEditingController( + languageCheckService: _debouncedLangService, + highlightStyle: widget.mistakeHighlightStyle, + onMistakeTap: ( + Offset globalPosition, + Color color, + Mistake mistake, + ) { + createPopupEntry( + globalPosition: globalPosition, + context: context, + mistake: mistake, + color: color, + ); + }, + ); + super.initState(); + } + @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(24.0), child: Center( child: TextField( - controller: widget.coloredController, + controller: _controller, style: widget.style, decoration: widget.decoration, ), diff --git a/lib/presentation/widgets/create_popup_entry.dart b/lib/presentation/widgets/create_popup_entry.dart new file mode 100644 index 0000000..c8224c8 --- /dev/null +++ b/lib/presentation/widgets/create_popup_entry.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:languagetool_textfield/languagetool_textfield.dart'; + +/// Creates a popup with mistake description on highlighted text tap +void createPopupEntry({ + required Offset globalPosition, + required BuildContext context, + required Mistake mistake, + required Color color, +}) { + showDialog( + context: context, + builder: (BuildContext context) { + ///Default value for radius, margin, circle size + const double defaultValue = 8; + + ///Capitalizing mistake type + final String mistakeType = + mistake.type.name[0].toUpperCase() + mistake.type.name.substring(1); + + return Dialog( + backgroundColor: Colors.transparent, + elevation: 0, + insetPadding: EdgeInsets.zero, + child: Stack( + children: [ + Positioned( + left: globalPosition.dx, + top: globalPosition.dy, + child: Material( + elevation: defaultValue, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(defaultValue), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + height: defaultValue, + width: defaultValue, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color, + ), + ), + const SizedBox( + width: defaultValue, + ), + Text( + mistakeType, + style: const TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + const SizedBox( + height: defaultValue, + ), + Text( + mistake.message, + style: const TextStyle( + color: Colors.black, + fontSize: 11, + ), + ), + ], + ), + ), + ), + ), + + /// Dismiss Dialog + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + ) + ], + ), + ); + }, + barrierColor: Colors.transparent, + ); +}