fluffychat/lib/pages/chat/events/html_message.dart

165 lines
5.5 KiB
Dart
Raw Normal View History

2020-05-09 13:36:41 +02:00
import 'package:flutter/material.dart';
2021-10-26 18:50:34 +02:00
import 'package:flutter_gen/gen_l10n/l10n.dart';
2021-10-26 18:50:34 +02:00
import 'package:flutter_matrix_html/flutter_html.dart';
import 'package:matrix/matrix.dart';
2021-11-09 21:32:16 +01:00
import 'package:fluffychat/widgets/matrix.dart';
import '../../../config/app_config.dart';
import '../../../config/setting_keys.dart';
import '../../../pages/image_viewer/image_viewer.dart';
2022-12-30 17:54:01 +01:00
import '../../../utils/matrix_sdk_extensions/matrix_locals.dart';
2021-11-09 21:32:16 +01:00
import '../../../utils/url_launcher.dart';
2020-05-09 13:36:41 +02:00
class HtmlMessage extends StatelessWidget {
final String html;
2022-01-29 12:35:03 +01:00
final int? maxLines;
2020-05-14 07:43:21 +02:00
final Room room;
2022-01-29 12:35:03 +01:00
final TextStyle? defaultTextStyle;
final TextStyle? linkStyle;
final double? emoteSize;
2020-05-09 13:36:41 +02:00
2021-10-14 18:09:30 +02:00
const HtmlMessage({
2022-01-29 12:35:03 +01:00
Key? key,
required this.html,
2021-10-14 18:09:30 +02:00
this.maxLines,
2022-01-29 12:35:03 +01:00
required this.room,
2021-10-14 18:09:30 +02:00
this.defaultTextStyle,
this.linkStyle,
this.emoteSize,
}) : super(key: key);
2020-05-09 13:36:41 +02:00
@override
Widget build(BuildContext context) {
// riot-web is notorious for creating bad reply fallback events from invalid messages which, if
// not handled properly, can lead to impersination. As such, we strip the entire `<mx-reply>` tags
// here already, to prevent that from happening.
// We do *not* do this in an AST and just with simple regex here, as riot-web tends to create
// miss-matching tags, and this way we actually correctly identify what we want to strip and, well,
// strip it.
final renderHtml = html.replaceAll(
RegExp(
'<mx-reply>.*</mx-reply>',
caseSensitive: false,
multiLine: false,
dotAll: true,
),
'',
);
2020-05-09 13:36:41 +02:00
// there is no need to pre-validate the html, as we validate it while rendering
2020-05-22 12:21:16 +02:00
2020-10-28 10:15:06 +01:00
final matrix = Matrix.of(context);
2020-05-15 07:47:32 +02:00
final themeData = Theme.of(context);
2020-05-09 13:36:41 +02:00
return Html(
data: renderHtml,
2020-05-15 07:47:32 +02:00
defaultTextStyle: defaultTextStyle,
2020-09-20 11:35:28 +02:00
emoteSize: emoteSize,
2020-05-22 12:21:16 +02:00
linkStyle: linkStyle ??
2023-01-26 09:47:30 +01:00
themeData.textTheme.bodyMedium!.copyWith(
2021-05-24 10:59:00 +02:00
color: themeData.colorScheme.secondary,
2020-05-22 12:21:16 +02:00
decoration: TextDecoration.underline,
2023-02-14 14:05:18 +01:00
decorationColor: themeData.colorScheme.secondary,
2020-05-22 12:21:16 +02:00
),
2020-05-09 13:36:41 +02:00
shrinkToFit: true,
maxLines: maxLines,
2020-09-05 13:45:03 +02:00
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
onPillTap: (url) => UrlLauncher(context, url).launchUrl(),
getMxcUrl: (
String mxc,
double? width,
double? height, {
bool? animated = false,
}) {
2020-05-09 13:36:41 +02:00
final ratio = MediaQuery.of(context).devicePixelRatio;
2021-04-21 14:19:54 +02:00
return Uri.parse(mxc)
2022-01-29 12:35:03 +01:00
.getThumbnail(
2021-04-21 14:19:54 +02:00
matrix.client,
width: (width ?? 800) * ratio,
height: (height ?? 800) * ratio,
method: ThumbnailMethod.scale,
animated: AppConfig.autoplayImages ? animated : false,
2021-04-21 14:19:54 +02:00
)
.toString();
2020-05-09 13:36:41 +02:00
},
onImageTap: (String mxc) => showDialog(
context: Matrix.of(context).navigatorContext,
useRootNavigator: false,
builder: (_) => ImageViewer(
Event(
type: EventTypes.Message,
content: <String, dynamic>{
'body': mxc,
'url': mxc,
'msgtype': MessageTypes.Image,
},
senderId: room.client.userID!,
originServerTs: DateTime.now(),
eventId: 'fake_event',
room: room,
),
),
),
2020-10-28 10:15:06 +01:00
setCodeLanguage: (String key, String value) async {
await matrix.store.setItem('${SettingKeys.codeLanguage}.$key', value);
2020-10-28 10:15:06 +01:00
},
getCodeLanguage: (String key) async {
return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key');
2020-10-28 10:15:06 +01:00
},
getPillInfo: (String url) async {
final identityParts = url.parseIdentifierIntoParts();
final identifier = identityParts?.primaryIdentifier;
if (identifier == null) {
return {};
}
if (identifier.sigil == '@') {
2020-05-14 07:43:21 +02:00
// we have a user pill
final user = room.getState('m.room.member', identifier);
if (user != null) {
return user.content;
}
// there might still be a profile...
final profile = await room.client.getProfileFromUserId(identifier);
2022-01-29 12:35:03 +01:00
return {
'displayname': profile.displayName,
'avatar_url': profile.avatarUrl.toString(),
};
2020-05-14 07:43:21 +02:00
}
if (identifier.sigil == '#') {
2020-05-14 07:43:21 +02:00
// we have an alias pill
for (final r in room.client.rooms) {
final state = r.getState('m.room.canonical_alias');
2020-05-22 12:21:16 +02:00
if (state != null &&
((state.content['alias'] is String &&
state.content['alias'] == identifier) ||
(state.content['alt_aliases'] is List &&
state.content['alt_aliases'].contains(identifier)))) {
2020-05-14 07:43:21 +02:00
// we have a room!
return {
'displayname':
2022-01-29 12:35:03 +01:00
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
2020-05-14 07:43:21 +02:00
'avatar_url': r.getState('m.room.avatar')?.content['url'],
};
}
}
return {};
2020-05-14 07:43:21 +02:00
}
if (identifier.sigil == '!') {
2020-05-14 07:43:21 +02:00
// we have a room ID pill
final r = room.client.getRoomById(identifier);
if (r == null) {
return {};
2020-05-14 07:43:21 +02:00
}
return {
'displayname':
2022-01-29 12:35:03 +01:00
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
2020-05-14 07:43:21 +02:00
'avatar_url': r.getState('m.room.avatar')?.content['url'],
};
}
return {};
2022-02-02 07:33:40 +01:00
},
2020-05-09 13:36:41 +02:00
);
}
}