LCOV - code coverage report
Current view: top level - lib/third_party/linkify - flutter_linkify.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 89 0.0 %
Date: 2024-10-08 18:18:56 Functions: 0 0 -

          Line data    Source code
       1             : // Code Originally taken from https://github.com/Cretezy/flutter_linkify/blob/201e147e0b07b7ca5c543da8167d712d81760753/lib/flutter_linkify.dart
       2             : //
       3             : // Now uses local `linkify`
       4             : //
       5             : // Original License for this code:
       6             : // MIT License
       7             : //     Copyright (c) 2020 Charles-William Crete
       8             : //
       9             : //     Permission is hereby granted, free of charge, to any person obtaining a copy
      10             : //     of this software and associated documentation files (the "Software"), to deal
      11             : //     in the Software without restriction, including without limitatifon the rights
      12             : //     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      13             : //     copies of the Software, and to permit persons to whom the Software is
      14             : //     furnished to do so, subject to the following conditions:
      15             : //
      16             : //     The above copyright notice and this permission notice shall be included in all
      17             : //     copies or substantial portions of the Software.
      18             : //
      19             : //     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      20             : //     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      21             : //     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      22             : //     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      23             : //     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      24             : //     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      25             : //     SOFTWARE.
      26             : 
      27             : import 'package:flutter/gestures.dart';
      28             : import 'package:flutter/material.dart';
      29             : 
      30             : import 'linkify.dart';
      31             : 
      32             : export 'linkify.dart' show LinkifyElement, LinkifyOptions, LinkableElement, TextElement, Linkifier, UrlElement, UrlLinkifier;
      33             : 
      34             : /// Callback clicked link
      35             : typedef LinkCallback = void Function(LinkableElement link);
      36             : 
      37             : /// Turns URLs into links
      38             : class SelectableLinkify extends StatelessWidget {
      39             :   /// Text to be linkified
      40             :   final String text;
      41             : 
      42             :   /// The number of font pixels for each logical pixel
      43             :   final textScaleFactor;
      44             : 
      45             :   /// Linkifiers to be used for linkify
      46             :   final List<Linkifier> linkifiers;
      47             : 
      48             :   /// Callback for tapping a link
      49             :   final LinkCallback? onOpen;
      50             : 
      51             :   /// linkify's options.
      52             :   final LinkifyOptions options;
      53             : 
      54             :   // TextSpan
      55             : 
      56             :   /// Style for code text
      57             :   final TextStyle codeStyle;
      58             : 
      59             :   /// Style for non-link text
      60             :   final TextStyle style;
      61             : 
      62             :   /// Style of link text
      63             :   final TextStyle linkStyle;
      64             : 
      65             :   // Text.rich
      66             : 
      67             :   /// How the text should be aligned horizontally.
      68             :   final TextAlign? textAlign;
      69             : 
      70             :   /// Text direction of the text
      71             :   final TextDirection? textDirection;
      72             : 
      73             :   /// The minimum number of lines to occupy when the content spans fewer lines.
      74             :   final int? minLines;
      75             : 
      76             :   /// The maximum number of lines for the text to span, wrapping if necessary
      77             :   final int? maxLines;
      78             : 
      79             :   /// The strut style used for the vertical layout
      80             :   final StrutStyle? strutStyle;
      81             : 
      82             :   /// Defines how to measure the width of the rendered text.
      83             :   final TextWidthBasis? textWidthBasis;
      84             : 
      85             :   // SelectableText.rich
      86             : 
      87             :   /// Defines the focus for this widget.
      88             :   final FocusNode? focusNode;
      89             : 
      90             :   /// Whether to show cursor
      91             :   final bool showCursor;
      92             : 
      93             :   /// Whether this text field should focus itself if nothing else is already focused.
      94             :   final bool autofocus;
      95             : 
      96             :   /// How thick the cursor will be
      97             :   final double cursorWidth;
      98             : 
      99             :   /// How rounded the corners of the cursor should be
     100             :   final Radius? cursorRadius;
     101             : 
     102             :   /// The color to use when painting the cursor
     103             :   final Color? cursorColor;
     104             : 
     105             :   /// Determines the way that drag start behavior is handled
     106             :   final DragStartBehavior dragStartBehavior;
     107             : 
     108             :   /// If true, then long-pressing this TextField will select text and show the cut/copy/paste menu,
     109             :   /// and tapping will move the text caret
     110             :   final bool enableInteractiveSelection;
     111             : 
     112             :   /// Called when the user taps on this selectable text (not link)
     113             :   final GestureTapCallback? onTap;
     114             : 
     115             :   final ScrollPhysics? scrollPhysics;
     116             : 
     117             :   /// Defines how the paragraph will apply TextStyle.height to the ascent of the first line and descent of the last line.
     118             :   final TextHeightBehavior? textHeightBehavior;
     119             : 
     120             :   /// How tall the cursor will be.
     121             :   final double? cursorHeight;
     122             : 
     123             :   /// Optional delegate for building the text selection handles and toolbar.
     124             :   final TextSelectionControls? selectionControls;
     125             : 
     126             :   /// Called when the user changes the selection of text (including the cursor location).
     127             :   final SelectionChangedCallback? onSelectionChanged;
     128             : 
     129             :   final BoxConstraints? constraints;
     130             : 
     131           0 :   const SelectableLinkify({
     132             :     Key? key,
     133             :     required this.text,
     134             :     this.linkifiers = defaultLinkifiers,
     135             :     this.onOpen,
     136             :     this.options = const LinkifyOptions(),
     137             :     // TextSpan
     138             :     required this.style,
     139             :     required this.linkStyle,
     140             :     // RichText
     141             :     this.textAlign,
     142             :     required this.codeStyle,
     143             :     this.textDirection,
     144             :     this.minLines,
     145             :     this.maxLines,
     146             :     // SelectableText
     147             :     this.focusNode,
     148             :     this.textScaleFactor = 1.0,
     149             :     this.strutStyle,
     150             :     this.showCursor = false,
     151             :     this.autofocus = false,
     152             :     this.cursorWidth = 2.0,
     153             :     this.cursorRadius,
     154             :     this.cursorColor,
     155             :     this.dragStartBehavior = DragStartBehavior.start,
     156             :     this.enableInteractiveSelection = true,
     157             :     this.onTap,
     158             :     this.scrollPhysics,
     159             :     this.textWidthBasis,
     160             :     this.textHeightBehavior,
     161             :     this.cursorHeight,
     162             :     this.selectionControls,
     163             :     this.onSelectionChanged,
     164             :     this.constraints,
     165           0 :   }) : super(key: key);
     166             : 
     167           0 :   @override
     168             :   Widget build(BuildContext context) {
     169           0 :     final elements = linkify(
     170           0 :       text,
     171           0 :       options: options,
     172           0 :       linkifiers: linkifiers,
     173             :     );
     174             : 
     175           0 :     return Container(
     176           0 :         constraints: constraints,
     177           0 :         child: SelectableText.rich(
     178           0 :           buildTextSpan(elements,
     179           0 :               style: style,
     180           0 :               codeStyle: codeStyle,
     181           0 :               onOpen: onOpen,
     182             :               context: context,
     183           0 :               linkStyle: linkStyle.copyWith(
     184             :                 decoration: TextDecoration.underline,
     185             :               ),
     186           0 :               constraints: constraints),
     187           0 :           textAlign: textAlign,
     188           0 :           textDirection: textDirection,
     189           0 :           minLines: minLines,
     190           0 :           maxLines: maxLines,
     191           0 :           focusNode: focusNode,
     192           0 :           strutStyle: strutStyle,
     193           0 :           showCursor: showCursor,
     194           0 :           textScaleFactor: textScaleFactor,
     195           0 :           autofocus: autofocus,
     196           0 :           cursorWidth: cursorWidth,
     197           0 :           cursorRadius: cursorRadius,
     198           0 :           cursorColor: cursorColor,
     199           0 :           dragStartBehavior: dragStartBehavior,
     200           0 :           enableInteractiveSelection: enableInteractiveSelection,
     201           0 :           onTap: onTap,
     202           0 :           scrollPhysics: scrollPhysics,
     203           0 :           textWidthBasis: textWidthBasis,
     204           0 :           textHeightBehavior: textHeightBehavior,
     205           0 :           cursorHeight: cursorHeight,
     206           0 :           selectionControls: selectionControls,
     207           0 :           onSelectionChanged: onSelectionChanged,
     208           0 :           style: style,
     209             :         ));
     210             :   }
     211             : }
     212             : 
     213             : class LinkableSpan extends WidgetSpan {
     214           0 :   LinkableSpan({
     215             :     required MouseCursor mouseCursor,
     216             :     required InlineSpan inlineSpan,
     217             :     required BuildContext context,
     218           0 :   }) : super(
     219           0 :           child: MouseRegion(
     220             :             cursor: mouseCursor,
     221           0 :             child: RichText(
     222             :               text: inlineSpan,
     223           0 :               selectionRegistrar: SelectionContainer.maybeOf(context),
     224             :             ),
     225             :           ),
     226             :         );
     227             : }
     228             : 
     229             : /// Raw TextSpan builder for more control on the RichText
     230           0 : TextSpan buildTextSpan(
     231             :   List<LinkifyElement> elements, {
     232             :   TextStyle? style,
     233             :   TextStyle? linkStyle,
     234             :   TextStyle? codeStyle,
     235             :   LinkCallback? onOpen,
     236             :   required BuildContext context,
     237             :   bool useMouseRegion = false,
     238             :   BoxConstraints? constraints,
     239             : }) {
     240             :   // Ok, so the problem here is that Flutter really wants to optimize this function
     241             :   // out of the rebuild process. This is fine when the screen gets smaller because
     242             :   // Flutter forces TextSpan to rebuild with the new constraints automatically.
     243             :   // HOWEVER, when the screen gets larger, Flutter seems to think that it doesn't
     244             :   // need to bother rebuilding this TextSpan because it already fits in the provided constraints.
     245             :   // To force a rebuild here we append a constraint-determined space character to the end of the
     246             :   // text element.
     247             :   // (I tried a few other things, including the docs-sanctioned MediaQuery.sizeOf(context) - which promises a rebuild
     248             :   // but Flutter is pretty good at optimizing "useless" checks out)
     249             :   String inlineText = "\u0020";
     250           0 :   if (constraints != null && constraints.maxWidth % 2 == 0) {
     251             :     inlineText = "\u00A0";
     252             :   }
     253           0 :   elements.add(TextElement(inlineText));
     254           0 :   return TextSpan(
     255             :     style: style,
     256           0 :     children: elements.map<InlineSpan>(
     257           0 :       (element) {
     258           0 :         if (element is LinkableElement) {
     259             :           if (useMouseRegion) {
     260           0 :             return TextSpan(
     261           0 :                 text: element.text,
     262             :                 style: linkStyle,
     263             :                 mouseCursor: SystemMouseCursors.click,
     264           0 :                 recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null,
     265           0 :                 semanticsLabel: element.text);
     266             :           } else {
     267           0 :             return TextSpan(
     268           0 :               text: element.text,
     269             :               style: linkStyle,
     270           0 :               recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null,
     271             :             );
     272             :           }
     273           0 :         } else if (element is BoldElement) {
     274           0 :           return TextSpan(text: element.text.replaceAll("*", ""), style: style?.copyWith(fontWeight: FontWeight.bold), semanticsLabel: element.text);
     275           0 :         } else if (element is ItalicElement) {
     276           0 :           return TextSpan(text: element.text.replaceAll("*", ""), style: style?.copyWith(fontStyle: FontStyle.italic), semanticsLabel: element.text);
     277           0 :         } else if (element is SuperElement) {
     278           0 :           return WidgetSpan(
     279           0 :               child: Transform.translate(
     280             :             offset: const Offset(2, -6),
     281           0 :             child: Text(element.text.replaceAll("^", ""),
     282             :                 //superscript is usually smaller in size
     283             :                 textScaleFactor: 0.7,
     284             :                 style: style,
     285           0 :                 semanticsLabel: element.text),
     286             :           ));
     287           0 :         } else if (element is SubElement) {
     288           0 :           return WidgetSpan(
     289           0 :               child: Transform.translate(
     290             :             offset: const Offset(2, 4),
     291           0 :             child: Text(element.text.replaceAll("_", ""),
     292             :                 //superscript is usually smaller in size
     293             :                 textScaleFactor: 0.7,
     294             :                 style: style,
     295           0 :                 semanticsLabel: element.text),
     296             :           ));
     297           0 :         } else if (element is StrikeElement) {
     298           0 :           return TextSpan(
     299           0 :               text: element.text.replaceAll("~~", ""),
     300           0 :               style: style?.copyWith(decoration: TextDecoration.lineThrough, decorationColor: style.color, decorationStyle: TextDecorationStyle.solid),
     301           0 :               semanticsLabel: element.text);
     302           0 :         } else if (element is CodeElement) {
     303           0 :           return TextSpan(
     304           0 :               text: element.text.replaceAll("\`", ""),
     305             :               // monospace fonts at the same size as regular text makes them appear
     306             :               // slightly larger, so we compensate by making them slightly smaller...
     307           0 :               style: codeStyle?.copyWith(fontFamily: "RobotoMono", fontSize: codeStyle.fontSize! - 1.5),
     308           0 :               semanticsLabel: element.text);
     309             :         } else {
     310           0 :           return TextSpan(
     311           0 :             text: element.text,
     312             :             style: style,
     313             :           );
     314             :         }
     315             :       },
     316           0 :     ).toList(),
     317             :   );
     318             : }
     319             : 
     320             : // Show a tooltip over an inlined element in a Rich Text widget.
     321             : class TooltipSpan extends WidgetSpan {
     322           0 :   TooltipSpan({
     323             :     required String message,
     324             :     required InlineSpan inlineSpan,
     325             :     required BuildContext context,
     326           0 :   }) : super(
     327           0 :           child: Tooltip(
     328             :             message: message,
     329           0 :             child: RichText(
     330             :               text: inlineSpan,
     331           0 :               selectionRegistrar: SelectionContainer.maybeOf(context),
     332             :             ),
     333             :           ),
     334             :         );
     335             : }

Generated by: LCOV version 1.14