Skip to content

Commit a58bd0e

Browse files
authored
Merge pull request #46 from 3nln/feat/preview-url-msg
feat: link preview with matrix api
2 parents 1f2ca14 + aed37e6 commit a58bd0e

4 files changed

Lines changed: 494 additions & 42 deletions

File tree

lib/pages/chat/events/html_message.dart

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import 'package:fluffychat/widgets/future_loading_dialog.dart';
66
import 'package:fluffychat/widgets/mxc_image.dart';
77
import 'package:flutter/cupertino.dart';
88
import 'package:flutter/material.dart';
9+
import 'package:flutter/gestures.dart';
910
import 'package:flutter_linkify/flutter_linkify.dart';
11+
import 'package:linkify/linkify.dart' as linkify_lib;
1012
import 'package:highlight/highlight.dart' show highlight;
1113
import 'package:html/dom.dart' as dom;
1214
import 'package:html/parser.dart' as parser;
@@ -137,9 +139,8 @@ class HtmlMessage extends StatelessWidget {
137139
// Single linebreak nodes between Elements are ignored:
138140
if (text == '\n') text = '';
139141

140-
return LinkifySpan(
142+
return _OriginTextLinkifySpan(
141143
text: text,
142-
options: const LinkifyOptions(humanize: false),
143144
linkStyle: linkStyle,
144145
onOpen: onOpen,
145146
);
@@ -493,3 +494,41 @@ extension on String {
493494
extension on dom.Element {
494495
dom.Element get rootElement => parent?.rootElement ?? this;
495496
}
497+
498+
class _OriginTextLinkifySpan extends TextSpan {
499+
_OriginTextLinkifySpan({
500+
required String text,
501+
TextStyle? linkStyle,
502+
void Function(LinkableElement)? onOpen,
503+
}) : super(
504+
children: _build(text, linkStyle, onOpen),
505+
);
506+
507+
static List<InlineSpan> _build(
508+
String text,
509+
TextStyle? linkStyle,
510+
void Function(LinkableElement)? onOpen,
511+
) {
512+
final elements = linkify_lib.linkify(
513+
text,
514+
options: const LinkifyOptions(
515+
humanize: false,
516+
looseUrl: true,
517+
defaultToHttps: true,
518+
),
519+
);
520+
return [
521+
for (final element in elements)
522+
if (element is LinkableElement)
523+
TextSpan(
524+
text: element.originText,
525+
style: linkStyle,
526+
recognizer: onOpen != null
527+
? (TapGestureRecognizer()..onTap = () => onOpen(element))
528+
: null,
529+
)
530+
else
531+
TextSpan(text: element.text),
532+
];
533+
}
534+
}

lib/pages/chat/events/message_content.dart

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import '../../../config/app_config.dart';
2020
import '../../../utils/event_checkbox_extension.dart';
2121
import '../../../utils/platform_infos.dart';
2222
import '../../../utils/url_launcher.dart';
23+
import '../../../utils/url_preview_service.dart';
2324
import 'audio_player.dart';
2425
import 'call_notify_event.dart';
2526
import 'cute_events.dart';
2627
import 'html_message.dart';
2728
import 'image_bubble.dart';
2829
import 'map_bubble.dart';
2930
import 'message_download_content.dart';
31+
import 'url_preview.dart';
3032

3133
class MessageContent extends StatelessWidget {
3234
final Event event;
@@ -285,51 +287,66 @@ class MessageContent extends StatelessWidget {
285287
final bigEmotes =
286288
!event.isRichMessage && bigEmojis.contains(event.body);
287289

290+
final urls = UrlPreviewService.extractUrls(event.body);
291+
288292
return Padding(
289293
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
290-
child: Stack(
294+
child: Column(
295+
crossAxisAlignment: CrossAxisAlignment.start,
296+
mainAxisSize: MainAxisSize.min,
291297
children: [
292-
HtmlMessage(
293-
html: html,
294-
textColor: textColor,
295-
room: event.room,
296-
fontSize: AppSettings.fontSizeFactor.value * AppConfig.messageFontSize * (bigEmotes ? 5 : 1),
297-
limitHeight: !selected,
298-
linkStyle: TextStyle(
299-
color: linkColor,
300-
fontSize: AppSettings.fontSizeFactor.value * AppConfig.messageFontSize,
301-
decoration: TextDecoration.underline,
302-
decorationColor: linkColor,
303-
),
304-
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
305-
eventId: event.eventId,
306-
checkboxCheckedEvents: event.aggregatedEvents(
307-
timeline,
308-
EventCheckboxRoomExtension.relationshipType,
309-
),
310-
trailingWidget: SizedBox(
311-
width: isReplied
312-
? double.infinity
313-
: messageStatus == null
314-
? 46
315-
: 60,
316-
height: 14,
317-
),
318-
),
298+
Stack(
299+
children: [
300+
HtmlMessage(
301+
html: html,
302+
textColor: textColor,
303+
room: event.room,
304+
fontSize: AppSettings.fontSizeFactor.value * AppConfig.messageFontSize * (bigEmotes ? 5 : 1),
305+
limitHeight: !selected,
306+
linkStyle: TextStyle(
307+
color: linkColor,
308+
fontSize: AppSettings.fontSizeFactor.value * AppConfig.messageFontSize,
309+
decoration: TextDecoration.underline,
310+
decorationColor: linkColor,
311+
),
312+
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
313+
eventId: event.eventId,
314+
checkboxCheckedEvents: event.aggregatedEvents(
315+
timeline,
316+
EventCheckboxRoomExtension.relationshipType,
317+
),
318+
trailingWidget: SizedBox(
319+
width: isReplied
320+
? double.infinity
321+
: messageStatus == null
322+
? 46
323+
: 60,
324+
height: 14,
325+
),
326+
),
319327

320-
// Time - always in the lower right corner
321-
Positioned(
322-
right: 0,
323-
bottom: 0,
324-
child: Row(
325-
mainAxisSize: MainAxisSize.min,
326-
children: [
327-
Text(formattedTime, style: TextStyle(color: textColor, fontSize: 12)),
328-
if (messageStatus != null) const SizedBox(width: 3),
329-
if (messageStatus != null) MessageStatusWidget(status: messageStatus, iconColor: textColor),
330-
],
331-
),
328+
// Time - always in the lower right corner
329+
Positioned(
330+
right: 0,
331+
bottom: 0,
332+
child: Row(
333+
mainAxisSize: MainAxisSize.min,
334+
children: [
335+
Text(formattedTime, style: TextStyle(color: textColor, fontSize: 12)),
336+
if (messageStatus != null) const SizedBox(width: 3),
337+
if (messageStatus != null) MessageStatusWidget(status: messageStatus, iconColor: textColor),
338+
],
339+
),
340+
),
341+
],
332342
),
343+
if (urls.isNotEmpty)
344+
UrlPreviewWidget(
345+
key: ValueKey('preview_${event.eventId}_${urls.first}'),
346+
url: urls.first,
347+
textColor: textColor,
348+
linkColor: linkColor,
349+
),
333350
],
334351
),
335352
);

0 commit comments

Comments
 (0)