Skip to content

Commit

Permalink
feat: add incomplete markdown support for text messages (#206)
Browse files Browse the repository at this point in the history
* feat: add incomplete markdown support for text messages

* feat: switch to using flutter_parsed_text for markdown subset

* feat: detect code as markdown

* feat: use new package to linkify and launch url + emails

Co-authored-by: Alex <alexdemchenko@yahoo.com>
  • Loading branch information
felixgabler and demchenkoalex authored Apr 9, 2022
1 parent 1464ae3 commit c08b4ed
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 28 deletions.
14 changes: 14 additions & 0 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,18 @@
android:name="flutterEmbedding"
android:value="2" />
</application>

<!-- Required for url and mailto launching via url_launcher -->
<queries>
<!-- If your app opens https URLs -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<!-- If your app sends emails -->
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="*/*" />
</intent>
</queries>
</manifest>
12 changes: 12 additions & 0 deletions example/assets/messages.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
[
{
"author": {
"firstName": "John",
"id": "b4878b96-efbc-479a-8291-474ef323dec7",
"imageUrl": "https://avatars.githubusercontent.com/u/14123304?v=4"
},
"createdAt": 1598438797000,
"id": "e7a673e9-86eb-4572-936f-2882b0183cdc",
"status": "seen",
"text": "**bold** _italic_ ~strikethrough~ `code`\nhttps://link-test.com test@email.com",
"type": "text"
},
{
"author": {
"firstName": "John",
Expand Down
5 changes: 5 additions & 0 deletions example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
<string>http</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
Expand Down
20 changes: 20 additions & 0 deletions lib/src/chat_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ abstract class ChatTheme {
required this.receivedEmojiMessageTextStyle,
this.receivedMessageBodyLinkTextStyle,
required this.receivedMessageBodyTextStyle,
this.receivedMessageBodyBoldTextStyle,
this.receivedMessageBodyCodeTextStyle,
required this.receivedMessageCaptionTextStyle,
required this.receivedMessageDocumentIconColor,
required this.receivedMessageLinkDescriptionTextStyle,
Expand All @@ -86,6 +88,8 @@ abstract class ChatTheme {
required this.sentEmojiMessageTextStyle,
this.sentMessageBodyLinkTextStyle,
required this.sentMessageBodyTextStyle,
this.sentMessageBodyBoldTextStyle,
this.sentMessageBodyCodeTextStyle,
required this.sentMessageCaptionTextStyle,
required this.sentMessageDocumentIconColor,
required this.sentMessageLinkDescriptionTextStyle,
Expand Down Expand Up @@ -175,6 +179,14 @@ abstract class ChatTheme {
/// of received messages
final TextStyle receivedMessageBodyTextStyle;

/// Body text style used for displaying bold text on received text messages.
/// Default to a bold version of [receivedMessageBodyTextStyle].
final TextStyle? receivedMessageBodyBoldTextStyle;

/// Body text style used for displaying code text on received text messages.
/// Defaults to a mono version of [receivedMessageBodyTextStyle].
final TextStyle? receivedMessageBodyCodeTextStyle;

/// Caption text style used for displaying secondary info (e.g. file size)
/// on different types of received messages
final TextStyle receivedMessageCaptionTextStyle;
Expand Down Expand Up @@ -215,6 +227,14 @@ abstract class ChatTheme {
/// of sent messages
final TextStyle sentMessageBodyTextStyle;

/// Body text style used for displaying bold text on sent text messages.
/// Defaults to a bold version of [sentMessageBodyTextStyle].
final TextStyle? sentMessageBodyBoldTextStyle;

/// Body text style used for displaying code text on sent text messages.
/// Defaults to a mono version of [sentMessageBodyTextStyle].
final TextStyle? sentMessageBodyCodeTextStyle;

/// Caption text style used for displaying secondary info (e.g. file size)
/// on different types of sent messages
final TextStyle sentMessageCaptionTextStyle;
Expand Down
117 changes: 89 additions & 28 deletions lib/src/widgets/text_message.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:flutter_link_previewer/flutter_link_previewer.dart'
show LinkPreview, regexLink;
show LinkPreview, regexEmail, regexLink;
import 'package:flutter_parsed_text/flutter_parsed_text.dart';
import 'package:url_launcher/url_launcher.dart';

import '../models/emoji_enlargement_behavior.dart';
import '../util.dart';
import 'inherited_chat_theme.dart';
Expand Down Expand Up @@ -50,12 +55,6 @@ class TextMessage extends StatelessWidget {
double width,
BuildContext context,
) {
final bodyLinkTextStyle = user.id == message.author.id
? InheritedChatTheme.of(context).theme.sentMessageBodyLinkTextStyle
: InheritedChatTheme.of(context).theme.receivedMessageBodyLinkTextStyle;
final bodyTextStyle = user.id == message.author.id
? InheritedChatTheme.of(context).theme.sentMessageBodyTextStyle
: InheritedChatTheme.of(context).theme.receivedMessageBodyTextStyle;
final linkDescriptionTextStyle = user.id == message.author.id
? InheritedChatTheme.of(context)
.theme
Expand All @@ -69,18 +68,9 @@ class TextMessage extends StatelessWidget {
.theme
.receivedMessageLinkTitleTextStyle;

final color = getUserAvatarNameColor(message.author,
InheritedChatTheme.of(context).theme.userAvatarNameColors);
final name = getUserName(message.author);

return LinkPreview(
enableAnimation: true,
header: showName ? name : null,
headerStyle: InheritedChatTheme.of(context)
.theme
.userNameTextStyle
.copyWith(color: color),
linkStyle: bodyLinkTextStyle ?? bodyTextStyle,
textWidget: _textWidgetBuilder(user, context, false),
metadataTextStyle: linkDescriptionTextStyle,
metadataTitleStyle: linkTitleTextStyle,
onPreviewDataFetched: _onPreviewDataFetched,
Expand All @@ -91,7 +81,6 @@ class TextMessage extends StatelessWidget {
),
previewData: message.previewData,
text: message.text,
textStyle: bodyTextStyle,
width: width,
);
}
Expand All @@ -105,6 +94,22 @@ class TextMessage extends StatelessWidget {
final color =
getUserAvatarNameColor(message.author, theme.userAvatarNameColors);
final name = getUserName(message.author);
final bodyTextStyle = user.id == message.author.id
? enlargeEmojis
? theme.sentEmojiMessageTextStyle
: theme.sentMessageBodyTextStyle
: enlargeEmojis
? theme.receivedEmojiMessageTextStyle
: theme.receivedMessageBodyTextStyle;
final boldTextStyle = user.id == message.author.id
? theme.sentMessageBodyBoldTextStyle
: theme.receivedMessageBodyBoldTextStyle;
final codeTextStyle = user.id == message.author.id
? theme.sentMessageBodyCodeTextStyle
: theme.receivedMessageBodyCodeTextStyle;
final bodyLinkTextStyle = user.id == message.author.id
? InheritedChatTheme.of(context).theme.sentMessageBodyLinkTextStyle
: InheritedChatTheme.of(context).theme.receivedMessageBodyLinkTextStyle;

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
Expand All @@ -119,16 +124,72 @@ class TextMessage extends StatelessWidget {
style: theme.userNameTextStyle.copyWith(color: color),
),
),
SelectableText(
message.text,
style: user.id == message.author.id
? enlargeEmojis
? theme.sentEmojiMessageTextStyle
: theme.sentMessageBodyTextStyle
: enlargeEmojis
? theme.receivedEmojiMessageTextStyle
: theme.receivedMessageBodyTextStyle,
textWidthBasis: TextWidthBasis.longestLine,
ParsedText(
text: message.text,
selectable: true,
style: bodyTextStyle,
regexOptions: const RegexOptions(multiLine: true, dotAll: true),
parse: [
MatchText(
pattern: regexEmail,
onTap: (mail) async {
final url = 'mailto:$mail';
if (await canLaunch(url)) {
await launch(url);
}
},
style: bodyLinkTextStyle ??
bodyTextStyle.copyWith(decoration: TextDecoration.underline),
),
MatchText(
pattern: regexLink,
onTap: (url) async {
if (await canLaunch(url)) {
await launch(url);
}
},
style: bodyLinkTextStyle ??
bodyTextStyle.copyWith(decoration: TextDecoration.underline),
),
MatchText(
pattern: '(\\*\\*|\\*)(.*?)(\\*\\*|\\*)',
onTap: (_) {},
style: boldTextStyle ??
bodyTextStyle.copyWith(fontWeight: FontWeight.bold),
renderText: ({required String str, required String pattern}) {
return {'display': str.replaceAll(RegExp('(\\*\\*|\\*)'), '')};
},
),
MatchText(
pattern: '_(.*?)_',
onTap: (_) {},
style: bodyTextStyle.copyWith(fontStyle: FontStyle.italic),
renderText: ({required String str, required String pattern}) {
return {'display': str.replaceAll('_', '')};
},
),
MatchText(
pattern: '~(.*?)~',
onTap: (_) {},
style: bodyTextStyle.copyWith(
decoration: TextDecoration.lineThrough,
),
renderText: ({required String str, required String pattern}) {
return {'display': str.replaceAll('~', '')};
},
),
MatchText(
pattern: '`(.*?)`',
onTap: (_) {},
style: codeTextStyle ??
bodyTextStyle.copyWith(
fontFamily: Platform.isIOS ? 'Courier' : 'monospace',
),
renderText: ({required String str, required String pattern}) {
return {'display': str.replaceAll('`', '')};
},
),
],
),
],
);
Expand Down
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ dependencies:
equatable: ^2.0.3
flutter_chat_types: ^3.3.2
flutter_link_previewer: ^2.6.4
flutter_parsed_text: ^2.2.1
intl: ^0.17.0
meta: ^1.7.0
photo_view: ^0.13.0
url_launcher: ^6.0.20
visibility_detector: ^0.2.2

dev_dependencies:
Expand Down

0 comments on commit c08b4ed

Please sign in to comment.