getSentenceList method

List<TextSpan> getSentenceList({
  1. required int maxShowCharactersLength,
  2. required TextStyle effectiveTextStyle,
  3. required TextStyle? defaultHighlightStyle,
  4. required List<TextHighlight> allTextHighlights,
  5. required Iterable<TextRange> allUrlRanges,
  6. required Map<int, TargetTextHighlights> allIndexTargetTextHighlightsMap,
  7. required void trackActiveTapGesture(
    1. TapGestureRecognizer recognizer
    ),
})

Implementation

List<TextSpan> getSentenceList({
  required int maxShowCharactersLength,
  required TextStyle effectiveTextStyle,
  required TextStyle? defaultHighlightStyle,
  required List<TextHighlight> allTextHighlights,
  required Iterable<TextRange> allUrlRanges,
  required Map<int, TargetTextHighlights> allIndexTargetTextHighlightsMap,
  required void Function(TapGestureRecognizer recognizer)
      trackActiveTapGesture,
}) {
  final allIndexes = Set<int>.unmodifiable(
    Iterable<int>.generate(maxShowCharactersLength + 1),
  );

  final textHighlightRangesIndexes = allTextHighlights
      .expand(
        (textHighlight) => textHighlight.highlightRanges.expand(
          (range) => range.rangeIndexes().toSet().intersection(allIndexes),
        ),
      )
      .toSet();

  final urlRanges =
      allUrlRanges.where((range) => range.start < maxShowCharactersLength);

  final urlHighlightIndexes = Set<int>.from(urlRanges.expand(
    (range) => range.rangeIndexes(),
  ));

  // We have to remove the index which are greater than endIndex.
  // As while creating urlRanges we check if start index is greater than
  // endIndex. And we do that because [_createUrlTextSpan] method takes
  // highlightText and fullUrl as parameter. So to get the fullUrl we
  // need to check if start index is greater than endIndex. Which means
  // even if the highlight portion of the url is trimmed, we need to
  // make sure that even if user click on the highlight portion,
  //  click should work. But by removing below we make sure that
  // while displaying the trimmed version we don't show any unwanted portion.
  urlHighlightIndexes.removeWhere((i) => i > maxShowCharactersLength);

  final allHighlightIndexesSet =
      textHighlightRangesIndexes.union(urlHighlightIndexes);

  final allHighlightIndexes = allHighlightIndexesSet.toList()..sort();
  final nonHighlightIndexes =
      allIndexes.difference(allHighlightIndexesSet).toList()..sort();
  final textSpans = <TextSpan>[];

  if (maxShowCharactersLength == 0) {
    return textSpans;
  }

  for (var i = 0; i < maxShowCharactersLength + 1; i++) {
    // We have -1 as end index is also increased by 1, and when next time
    // the loop is execute it will be increase by one as [i++]. So,
    // we need to decrease it by 1 to normalize it.
    final indexValue = allIndexes.elementAt(i == 0 ? 0 : i - 1);

    if (nonHighlightIndexes.contains(indexValue)) {
      final nonHighlightRange =
          nonHighlightIndexes.firstTextRangeFromStartIndex(
        indexValue,
        validateExpectEndIndexValue: (expectEndIndexValue) {
          if (allIndexes.contains(expectEndIndexValue)) {
            return expectEndIndexValue;
          }
          return allIndexes.last;
        },
      );

      final text = nonHighlightRange.textInside(data);
      textSpans.add(
        TextSpan(
          text: text,
          style: effectiveTextStyle,
        ),
      );

      i = nonHighlightRange.end;
      continue;
    }

    final highlightRange = allHighlightIndexes.firstTextRangeFromStartIndex(
      indexValue,
      validateExpectEndIndexValue: (expectEndIndexValue) {
        if (allIndexes.contains(expectEndIndexValue)) {
          return expectEndIndexValue;
        }
        return allIndexes.last;
      },
    );

    final highlightRangeIndexes = highlightRange.rangeIndexes().toSet();

    final urlRange = allUrlRanges.firstWhereOrNull(
      (urlRange) =>
          highlightRangeIndexes.containsAll(urlRange.rangeIndexes()),
    );

    if (urlRange != null) {
      final fullUrl = urlRange.textInside(data);
      var url = fullUrl;

      final urlRangeIndexes = urlRange.rangeIndexes().toSet();
      final targetSubStringHighlightIndexes =
          textHighlightRangesIndexes.intersection(urlRangeIndexes);
      onTapLinkCallback() async {
        if (!url.startsWith('http://') && !url.startsWith('https://')) {
          url = 'https://$fullUrl';
        }

        onTapLink?.call(fullUrl);
        if (onTapLink != null) {
        } else {
          try {
            final launchUri = Uri.parse(url);
            await launchUrl(launchUri, mode: LaunchMode.externalApplication);
          } on Exception catch (e) {
            throw Exception(e);
          }
        }
      }

      final urlChildren = _urlSubStringHighlightTextSpans(
        urlRangeIndexes: urlRangeIndexes,
        targetSubStringHighlightIndexes: targetSubStringHighlightIndexes,
        effectiveTextStyle: effectiveTextStyle,
        defaultHighlightStyle: defaultHighlightStyle,
        overrideOnTap: (_) {
          onTapLinkCallback();
        },
        shouldApplyHighlight: (targetHighlight) {
          return targetHighlight.highlightInUrl;
        },
        allIndexTargetTextHighlightsMap: allIndexTargetTextHighlightsMap,
        trackActiveTapGesture: trackActiveTapGesture,
      );

      final firstHighlightSubstring = urlChildren.isEmpty
          ? fullUrl
          : targetSubStringHighlightIndexes.contains(urlRange.start)
              ? targetSubStringHighlightIndexes
                  .firstTextRangeFromStartIndex(urlRange.start)
                  .textInside(data)
              : null;

      textSpans.add(
        _createUrlTextSpan(
          firstHighlightSubstring: firstHighlightSubstring,
          fullUrl: fullUrl,
          children: urlChildren,
          effectiveTextStyle: effectiveTextStyle,
          onTapLinkCallback: onTapLinkCallback,
        ),
      );
    } else {
      final highlightTextSpans = _highlightTextSpans(
        highlightRange: highlightRange,
        highlightRangeIndexes: highlightRangeIndexes,
        effectiveTextStyle: effectiveTextStyle,
        defaultHighlightStyle: defaultHighlightStyle,
        allIndexTargetTextHighlightsMap: allIndexTargetTextHighlightsMap,
        trackActiveTapGesture: trackActiveTapGesture,
      );
      textSpans.addAll(highlightTextSpans);
    }

    i = highlightRange.end;
  }

  return textSpans;
}