Skip to content

Instantly share code, notes, and snippets.

@BarryDaBee
Last active December 29, 2022 08:50
Show Gist options
  • Save BarryDaBee/f437cf168e79e7c7c6b68b0cdc27600b to your computer and use it in GitHub Desktop.
Save BarryDaBee/f437cf168e79e7c7c6b68b0cdc27600b to your computer and use it in GitHub Desktop.
Read More text with support for @mention. To be extended to accept a map of indicators and corresponding textStyles
class ReadMoreWithMention extends StatefulWidget {
final String text;
final TextStyle? textStyle;
final String readMoreText;
final TextStyle? readMoreTextStyle;
final int maxLines;
final int steps;
// final bool shouldUseSteps;
const ReadMoreWithMention({
Key? key,
this.readMoreText = 'Read more',
this.readMoreTextStyle,
this.maxLines = 4,
required this.text,
this.textStyle,
this.steps = 4,
// required this.shouldUseSteps,
}) : super(key: key);
@override
State<ReadMoreWithMention> createState() => _ReadMoreWithMentionState();
}
class _ReadMoreWithMentionState extends State<ReadMoreWithMention> {
late int _maxLines = widget.maxLines;
String get _readMoreText => ' ${widget.readMoreText}';
@override
Widget build(BuildContext context) {
return _buildMessage();
}
TextSpan _bodyTextSpan(text) {
return TextSpan(text: text, style: widget.textStyle);
}
TextSpan _readMoreTextSpan() {
return TextSpan(
text: _readMoreText,
style: widget.readMoreTextStyle,
recognizer: TapGestureRecognizer()
..onTap = () {
setState(() {
_maxLines += widget.steps;
});
},
);
}
TextSpan _mentionTextSpan(String text){
return TextSpan(
text: text,
style: const TextStyle(
color: Colors.blue,
fontSize: 14,
decoration: TextDecoration.underline,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
recognizer: TapGestureRecognizer()
..onTap = (){
print('You clicked on $text');
}
);
}
List<TextSpan> _buildTextSpanChildrenFromString(String text){
return text.split(' ').map((text){
if (text.startsWith('@')){
return _mentionTextSpan('$text ');
}else{
return _bodyTextSpan('$text ');
}
}).toList();
}
RichText _buildMessage() {
const double maxWidth = 300;
const double minWidth = 0;
TextPainter textPainter = TextPainter(
text: _readMoreTextSpan(),
textDirection: TextDirection.ltr,
maxLines: _maxLines,
ellipsis: '',
);
textPainter.layout(minWidth: minWidth, maxWidth: maxWidth);
final linkSize = textPainter.size;
textPainter.text = _bodyTextSpan(widget.text);
textPainter.layout(minWidth: minWidth, maxWidth: maxWidth);
final textSize = textPainter.size;
int? endIndex;
final pos = textPainter.getPositionForOffset(Offset(
textSize.width - linkSize.width,
textSize.height,
));
endIndex = textPainter.getOffsetBefore(pos.offset);
TextSpan textSpan;
final truncatedText = widget.text.substring(0, endIndex);
final truncatedTextSpanList = _buildTextSpanChildrenFromString(truncatedText);
final untruncatedTextSpanList = _buildTextSpanChildrenFromString(widget.text);
if (textPainter.didExceedMaxLines) {
textSpan = TextSpan(
children: <TextSpan>[...truncatedTextSpanList, _readMoreTextSpan()],
);
} else {
textSpan = TextSpan(
children: untruncatedTextSpanList,
);
}
return RichText(
softWrap: true,
text: textSpan,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment