mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-11-27 14:59:29 +01:00
Merge branch 'krille/reimplement-flutter-matrix-html' into 'main'
refactor: Reimplement flutter matrix html locally See merge request famedly/fluffychat!1119
This commit is contained in:
commit
abbf18e1dc
@ -1,31 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_matrix_html/flutter_html.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_highlighter/flutter_highlighter.dart';
|
||||
import 'package:flutter_highlighter/themes/shades-of-purple.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_math_fork/flutter_math.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../../config/app_config.dart';
|
||||
import '../../../config/setting_keys.dart';
|
||||
import '../../../utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import '../../../utils/url_launcher.dart';
|
||||
|
||||
class HtmlMessage extends StatelessWidget {
|
||||
final String html;
|
||||
final int? maxLines;
|
||||
final Room room;
|
||||
final TextStyle? defaultTextStyle;
|
||||
final TextStyle? linkStyle;
|
||||
final double? emoteSize;
|
||||
final Color? textColor;
|
||||
|
||||
const HtmlMessage({
|
||||
Key? key,
|
||||
required this.html,
|
||||
this.maxLines,
|
||||
required this.room,
|
||||
this.defaultTextStyle,
|
||||
this.linkStyle,
|
||||
this.emoteSize,
|
||||
this.textColor,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -46,101 +41,315 @@ class HtmlMessage extends StatelessWidget {
|
||||
'',
|
||||
);
|
||||
|
||||
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
|
||||
|
||||
// there is no need to pre-validate the html, as we validate it while rendering
|
||||
|
||||
final matrix = Matrix.of(context);
|
||||
|
||||
final themeData = Theme.of(context);
|
||||
return Html(
|
||||
data: renderHtml,
|
||||
defaultTextStyle: defaultTextStyle,
|
||||
emoteSize: emoteSize,
|
||||
linkStyle: linkStyle ??
|
||||
themeData.textTheme.bodyMedium!.copyWith(
|
||||
color: themeData.colorScheme.secondary,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: themeData.colorScheme.secondary,
|
||||
),
|
||||
shrinkToFit: true,
|
||||
maxLines: maxLines,
|
||||
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
|
||||
onPillTap: (url) => UrlLauncher(context, url).launchUrl(),
|
||||
getMxcUrl: (
|
||||
String mxc,
|
||||
double? width,
|
||||
double? height, {
|
||||
bool? animated = false,
|
||||
}) {
|
||||
final ratio = MediaQuery.of(context).devicePixelRatio;
|
||||
return Uri.parse(mxc)
|
||||
.getThumbnail(
|
||||
matrix.client,
|
||||
width: (width ?? 800) * ratio,
|
||||
height: (height ?? 800) * ratio,
|
||||
method: ThumbnailMethod.scale,
|
||||
animated: AppConfig.autoplayImages ? animated : false,
|
||||
)
|
||||
.toString();
|
||||
style: {
|
||||
'*': Style(
|
||||
color: textColor,
|
||||
margin: Margins.all(0),
|
||||
fontSize: FontSize(fontSize),
|
||||
),
|
||||
'a': Style(color: textColor?.withAlpha(150)),
|
||||
'h1': Style(fontSize: FontSize(fontSize * 3)),
|
||||
'h2': Style(fontSize: FontSize(fontSize * 2.5)),
|
||||
'h3': Style(fontSize: FontSize(fontSize * 2)),
|
||||
'h4': Style(fontSize: FontSize(fontSize * 1.75)),
|
||||
'h5': Style(fontSize: FontSize(fontSize * 1.5)),
|
||||
'h6': Style(fontSize: FontSize(fontSize * 1.25)),
|
||||
// Otherwise list tiles are all in the same row$$
|
||||
'li': Style(display: Display.block),
|
||||
},
|
||||
onImageTap: (url) => UrlLauncher(context, url).launchUrl(),
|
||||
setCodeLanguage: (String key, String value) async {
|
||||
await matrix.store.setItem('${SettingKeys.codeLanguage}.$key', value);
|
||||
},
|
||||
getCodeLanguage: (String key) async {
|
||||
return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key');
|
||||
},
|
||||
getPillInfo: (String url) async {
|
||||
final identityParts = url.parseIdentifierIntoParts();
|
||||
final identifier = identityParts?.primaryIdentifier;
|
||||
if (identifier == null) {
|
||||
return {};
|
||||
}
|
||||
if (identifier.sigil == '@') {
|
||||
// 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);
|
||||
return {
|
||||
'displayname': profile.displayName,
|
||||
'avatar_url': profile.avatarUrl.toString(),
|
||||
};
|
||||
}
|
||||
if (identifier.sigil == '#') {
|
||||
// we have an alias pill
|
||||
for (final r in room.client.rooms) {
|
||||
final state = r.getState('m.room.canonical_alias');
|
||||
if (state != null &&
|
||||
((state.content['alias'] is String &&
|
||||
state.content['alias'] == identifier) ||
|
||||
(state.content['alt_aliases'] is List &&
|
||||
state.content['alt_aliases'].contains(identifier)))) {
|
||||
// we have a room!
|
||||
return {
|
||||
'displayname':
|
||||
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
|
||||
'avatar_url': r.getState('m.room.avatar')?.content['url'],
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
if (identifier.sigil == '!') {
|
||||
// we have a room ID pill
|
||||
final r = room.client.getRoomById(identifier);
|
||||
if (r == null) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
'displayname':
|
||||
r.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
|
||||
'avatar_url': r.getState('m.room.avatar')?.content['url'],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
extensions: [
|
||||
UserPillExtension(context, room),
|
||||
RoomPillExtension(context, room.client),
|
||||
CodeExtension(fontSize: fontSize),
|
||||
MatrixMathExtension(
|
||||
style: TextStyle(fontSize: fontSize, color: textColor),
|
||||
),
|
||||
],
|
||||
onLinkTap: (url, _, __) => UrlLauncher(context, url).launchUrl(),
|
||||
onlyRenderTheseTags: const {
|
||||
...allowedHtmlTags,
|
||||
// Needed to make it work properly
|
||||
'body',
|
||||
'html',
|
||||
},
|
||||
shrinkWrap: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// Keep in sync with: https://spec.matrix.org/v1.6/client-server-api/#mroommessage-msgtypes
|
||||
static const Set<String> allowedHtmlTags = {
|
||||
'font',
|
||||
'del',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'blockquote',
|
||||
'p',
|
||||
'a',
|
||||
'ul',
|
||||
'ol',
|
||||
'sup',
|
||||
'sub',
|
||||
'li',
|
||||
'b',
|
||||
'i',
|
||||
'u',
|
||||
'strong',
|
||||
'em',
|
||||
'strike',
|
||||
'code',
|
||||
'hr',
|
||||
'br',
|
||||
'div',
|
||||
'table',
|
||||
'thead',
|
||||
'tbody',
|
||||
'tr',
|
||||
'th',
|
||||
'td',
|
||||
'caption',
|
||||
'pre',
|
||||
'span',
|
||||
'img',
|
||||
'details',
|
||||
'summary'
|
||||
};
|
||||
}
|
||||
|
||||
class MatrixMathExtension extends HtmlExtension {
|
||||
final TextStyle? style;
|
||||
|
||||
MatrixMathExtension({this.style});
|
||||
@override
|
||||
Set<String> get supportedTags => {'div'};
|
||||
|
||||
@override
|
||||
bool matches(ExtensionContext context) {
|
||||
if (context.elementName != 'div') return false;
|
||||
final mathData = context.element?.attributes['data-mx-maths'];
|
||||
return mathData != null;
|
||||
}
|
||||
|
||||
@override
|
||||
InlineSpan build(
|
||||
ExtensionContext context,
|
||||
Map<StyledElement, InlineSpan> Function() parseChildren,
|
||||
) {
|
||||
final data = context.element?.attributes['data-mx-maths'] ?? '';
|
||||
return WidgetSpan(
|
||||
child: Math.tex(
|
||||
data,
|
||||
textStyle: style,
|
||||
onErrorFallback: (e) {
|
||||
Logs().d('Flutter math parse error', e);
|
||||
return Text(
|
||||
data,
|
||||
style: style,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CodeExtension extends HtmlExtension {
|
||||
final double fontSize;
|
||||
|
||||
CodeExtension({required this.fontSize});
|
||||
@override
|
||||
Set<String> get supportedTags => {'code'};
|
||||
|
||||
@override
|
||||
InlineSpan build(
|
||||
ExtensionContext context,
|
||||
Map<StyledElement, InlineSpan> Function() parseChildren,
|
||||
) =>
|
||||
WidgetSpan(
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: HighlightView(
|
||||
context.element?.text ?? '',
|
||||
language: context.element?.className
|
||||
.split(' ')
|
||||
.singleWhereOrNull(
|
||||
(className) => className.startsWith('language-'),
|
||||
)
|
||||
?.split('language-')
|
||||
.last ??
|
||||
'md',
|
||||
theme: shadesOfPurpleTheme,
|
||||
padding: const EdgeInsets.all(6),
|
||||
textStyle: TextStyle(fontSize: fontSize),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class UserPillExtension extends HtmlExtension {
|
||||
final Room room;
|
||||
final BuildContext context;
|
||||
|
||||
UserPillExtension(this.context, this.room);
|
||||
@override
|
||||
Set<String> get supportedTags => {'a'};
|
||||
|
||||
@override
|
||||
bool matches(ExtensionContext context) {
|
||||
if (context.elementName != 'a') return false;
|
||||
final href = context.element?.attributes['href'];
|
||||
if (href == null) return false;
|
||||
final uri = Uri.tryParse(href);
|
||||
if (uri == null || uri.host != 'matrix.to') return false;
|
||||
final userId = uri.fragment.split('/').last;
|
||||
if (userId.sigil != '@') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
InlineSpan build(
|
||||
ExtensionContext context,
|
||||
Map<StyledElement, InlineSpan> Function() parseChildren,
|
||||
) {
|
||||
final href = context.element!.attributes['href']!;
|
||||
final uri = Uri.parse(href);
|
||||
final userId = uri.fragment.split('/').last;
|
||||
final user = room.unsafeGetUserFromMemoryOrFallback(userId);
|
||||
return WidgetSpan(
|
||||
child: MatrixPill(
|
||||
name: user.calcDisplayname(),
|
||||
avatar: user.avatarUrl,
|
||||
uri: href,
|
||||
outerContext: this.context,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RoomPillExtension extends HtmlExtension {
|
||||
final Client client;
|
||||
final BuildContext context;
|
||||
|
||||
RoomPillExtension(this.context, this.client);
|
||||
@override
|
||||
Set<String> get supportedTags => {'a'};
|
||||
|
||||
@override
|
||||
bool matches(ExtensionContext context) {
|
||||
if (context.elementName != 'a') return false;
|
||||
final href = context.element?.attributes['href'];
|
||||
if (href == null) return false;
|
||||
final uri = Uri.tryParse(href);
|
||||
if (uri == null || uri.host != 'matrix.to') return false;
|
||||
final roomId = Uri.decodeComponent(uri.fragment.split('/').last);
|
||||
if (!{'#', '!'}.contains(roomId.sigil)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
InlineSpan build(
|
||||
ExtensionContext context,
|
||||
Map<StyledElement, InlineSpan> Function() parseChildren,
|
||||
) {
|
||||
final href = context.element!.attributes['href']!;
|
||||
final uri = Uri.parse(href);
|
||||
final roomId = Uri.decodeComponent(uri.fragment.split('/').last);
|
||||
|
||||
final room = roomId.sigil == '!'
|
||||
? client.getRoomById(roomId)
|
||||
: client.getRoomByAlias(roomId);
|
||||
if (room != null) {
|
||||
return WidgetSpan(
|
||||
child: MatrixPill(
|
||||
name: room.getLocalizedDisplayname(),
|
||||
avatar: room.avatar,
|
||||
uri: href,
|
||||
outerContext: this.context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return WidgetSpan(
|
||||
child: FutureBuilder<QueryPublicRoomsResponse>(
|
||||
future: client.queryPublicRooms(
|
||||
server: roomId.domain,
|
||||
filter: PublicRoomQueryFilter(
|
||||
genericSearchTerm: roomId,
|
||||
),
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
final room = snapshot.data;
|
||||
return MatrixPill(
|
||||
name: room?.chunk.singleOrNull?.name ?? roomId,
|
||||
avatar: room?.chunk.singleOrNull?.avatarUrl,
|
||||
uri: href,
|
||||
outerContext: this.context,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MatrixPill extends StatelessWidget {
|
||||
final String name;
|
||||
final BuildContext outerContext;
|
||||
final Uri? avatar;
|
||||
final String uri;
|
||||
|
||||
const MatrixPill({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.outerContext,
|
||||
this.avatar,
|
||||
required this.uri,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: UrlLauncher(outerContext, uri).launchUrl,
|
||||
child: Material(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
side: BorderSide(
|
||||
color: Theme.of(outerContext).colorScheme.onPrimaryContainer,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
color: Theme.of(outerContext).colorScheme.primaryContainer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Avatar(
|
||||
mxContent: avatar,
|
||||
name: name,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(outerContext).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix_link_text/link_text.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat/events/video_player.dart';
|
||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
||||
@ -150,23 +150,10 @@ class MessageContent extends StatelessWidget {
|
||||
if (event.messageType == MessageTypes.Emote) {
|
||||
html = '* $html';
|
||||
}
|
||||
final bigEmotes = event.onlyEmotes &&
|
||||
event.numberEmotes > 0 &&
|
||||
event.numberEmotes <= 10;
|
||||
return HtmlMessage(
|
||||
html: html,
|
||||
defaultTextStyle: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: textColor.withAlpha(150),
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: textColor.withAlpha(150),
|
||||
),
|
||||
textColor: textColor,
|
||||
room: event.room,
|
||||
emoteSize: bigEmotes ? fontSize * 3 : fontSize * 1.5,
|
||||
);
|
||||
}
|
||||
// else we fall through to the normal message rendering
|
||||
@ -242,13 +229,13 @@ class MessageContent extends StatelessWidget {
|
||||
hideReply: true,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
return LinkText(
|
||||
return Linkify(
|
||||
text: snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
hideReply: true,
|
||||
),
|
||||
textStyle: TextStyle(
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: bigEmotes ? fontSize * 3 : fontSize,
|
||||
decoration:
|
||||
@ -260,10 +247,7 @@ class MessageContent extends StatelessWidget {
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: textColor.withAlpha(150),
|
||||
),
|
||||
beforeLaunch: (url) {
|
||||
UrlLauncher(context, url.toString()).launchUrl();
|
||||
return false;
|
||||
},
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -5,7 +5,6 @@ import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import '../../../config/app_config.dart';
|
||||
import 'html_message.dart';
|
||||
|
||||
class ReplyContent extends StatelessWidget {
|
||||
final Event replyEvent;
|
||||
@ -26,47 +25,23 @@ class ReplyContent extends StatelessWidget {
|
||||
final displayEvent =
|
||||
timeline != null ? replyEvent.getDisplayEvent(timeline) : replyEvent;
|
||||
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
|
||||
if (AppConfig.renderHtml &&
|
||||
[EventTypes.Message, EventTypes.Encrypted]
|
||||
.contains(displayEvent.type) &&
|
||||
[MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote]
|
||||
.contains(displayEvent.messageType) &&
|
||||
!displayEvent.redacted &&
|
||||
displayEvent.content['format'] == 'org.matrix.custom.html' &&
|
||||
displayEvent.content['formatted_body'] is String) {
|
||||
String? html = displayEvent.content['formatted_body'];
|
||||
if (displayEvent.messageType == MessageTypes.Emote) {
|
||||
html = '* $html';
|
||||
}
|
||||
replyBody = HtmlMessage(
|
||||
html: html!,
|
||||
defaultTextStyle: TextStyle(
|
||||
color: ownMessage
|
||||
? Theme.of(context).colorScheme.onPrimary
|
||||
: Theme.of(context).colorScheme.onBackground,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
maxLines: 1,
|
||||
room: displayEvent.room,
|
||||
emoteSize: fontSize * 1.5,
|
||||
);
|
||||
} else {
|
||||
replyBody = Text(
|
||||
displayEvent.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: ownMessage
|
||||
? Theme.of(context).colorScheme.onPrimary
|
||||
: Theme.of(context).colorScheme.onBackground,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replyBody = Text(
|
||||
displayEvent.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
withSenderNamePrefix: false,
|
||||
hideReply: true,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: ownMessage
|
||||
? Theme.of(context).colorScheme.onPrimary
|
||||
: Theme.of(context).colorScheme.onBackground,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
);
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
|
@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix_link_text/link_text.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
@ -101,7 +101,7 @@ class PinnedEvents extends StatelessWidget {
|
||||
hideReply: true,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
return LinkText(
|
||||
return Linkify(
|
||||
text: snapshot.data ??
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
@ -109,7 +109,7 @@ class PinnedEvents extends StatelessWidget {
|
||||
hideReply: true,
|
||||
),
|
||||
maxLines: 2,
|
||||
textStyle: TextStyle(
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@ -126,10 +126,8 @@ class PinnedEvents extends StatelessWidget {
|
||||
decorationColor:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
beforeLaunch: (url) {
|
||||
UrlLauncher(context, url.toString()).launchUrl();
|
||||
return false;
|
||||
},
|
||||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url).launchUrl(),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix_link_text/link_text.dart';
|
||||
import 'package:vrouter/vrouter.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
@ -125,13 +125,13 @@ class ChatDetailsView extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
),
|
||||
child: LinkText(
|
||||
child: Linkify(
|
||||
text: room.topic.isEmpty
|
||||
? L10n.of(context)!.addGroupDescription
|
||||
: room.topic,
|
||||
linkStyle:
|
||||
const TextStyle(color: Colors.blueAccent),
|
||||
textStyle: TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
@ -142,11 +142,8 @@ class ChatDetailsView extends StatelessWidget {
|
||||
.bodyMedium!
|
||||
.color,
|
||||
),
|
||||
beforeLaunch: (url) {
|
||||
UrlLauncher(context, url.toString())
|
||||
.launchUrl();
|
||||
return false;
|
||||
},
|
||||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
@ -3,8 +3,8 @@ import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix_link_text/link_text.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
@ -273,15 +273,13 @@ class StoryView extends StatelessWidget {
|
||||
controller.storyThemeData.alignmentY.toDouble() / 100,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: LinkText(
|
||||
child: Linkify(
|
||||
text: controller.loadingMode
|
||||
? L10n.of(context)!.loadingPleaseWait
|
||||
: event.content.tryGet<String>('body') ?? '',
|
||||
textAlign: TextAlign.center,
|
||||
beforeLaunch: (url) {
|
||||
UrlLauncher(context, url.toString()).launchUrl();
|
||||
return false;
|
||||
},
|
||||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url).launchUrl(),
|
||||
linkStyle: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Colors.blue.shade50,
|
||||
@ -291,7 +289,7 @@ class StoryView extends StatelessWidget {
|
||||
? null
|
||||
: textShadows,
|
||||
),
|
||||
textStyle: TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Colors.white,
|
||||
shadows: event.messageType == MessageTypes.Text
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix_link_text/link_text.dart';
|
||||
import 'package:vrouter/vrouter.dart';
|
||||
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
@ -153,17 +153,15 @@ class PublicRoomBottomSheet extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
subtitle: LinkText(
|
||||
subtitle: Linkify(
|
||||
text: profile!.topic!,
|
||||
linkStyle: const TextStyle(color: Colors.blueAccent),
|
||||
textStyle: TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodyMedium!.color,
|
||||
),
|
||||
beforeLaunch: (url) {
|
||||
UrlLauncher(context, url.toString()).launchUrl();
|
||||
return false;
|
||||
},
|
||||
onOpen: (url) =>
|
||||
UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
158
pubspec.lock
158
pubspec.lock
@ -113,30 +113,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
cached_network_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image
|
||||
sha256: fd3d0dc1d451f9a252b32d95d3f0c3c487bc41a75eba2e6097cb0b9c71491b15
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.3"
|
||||
cached_network_image_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_platform_interface
|
||||
sha256: bb2b8403b4ccdc60ef5f25c70dead1f3d32d24b9d6117cfc087f496b178594a7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
cached_network_image_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_web
|
||||
sha256: b8eb814ebfcb4dea049680f8c1ffb2df399e4d03bf7a352c775e26fa06e02fa0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
callkeep:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -467,14 +443,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.10.0"
|
||||
flutter_highlight:
|
||||
dependency: transitive
|
||||
flutter_highlighter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_highlight
|
||||
sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c"
|
||||
name: flutter_highlighter
|
||||
sha256: "93173afd47a9ada53f3176371755e7ea4a1065362763976d06d6adfb4d946e10"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
version: "0.1.1"
|
||||
flutter_html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_html
|
||||
sha256: "850c07bc6c1ed060d3eb3e88469a598260a13eb45d8978b197c1348e0a2b101f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-beta.1"
|
||||
flutter_keyboard_visibility:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -523,6 +507,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
flutter_linkify:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_linkify
|
||||
sha256: "74669e06a8f358fee4512b4320c0b80e51cffc496607931de68d28f099254073"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -569,22 +561,13 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_math_fork:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: flutter_math_fork
|
||||
sha256: "012ca684618b74286ab84fcc2f0e8d66b03ad623b8f79aea738f6b9c287f432c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
flutter_matrix_html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "krille/fix-trying-modify-unmodifiable-list"
|
||||
resolved-ref: "97ab2982ff0429167436e15c3ae3c1b23d630c05"
|
||||
url: "https://github.com/Sorunome/flutter_matrix_html.git"
|
||||
source: git
|
||||
version: "1.1.0"
|
||||
name: flutter_math_fork
|
||||
sha256: a143a3a89131b578043ecbdb5e759c1033a1b3e9174f5cd1b979d93f4a7fb41c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
flutter_native_splash:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -678,10 +661,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2"
|
||||
sha256: f991fdb1533c3caeee0cdc14b04f50f0c3916f0dbcbc05237ccbe4e3c6b93f3f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.6"
|
||||
version: "2.0.5"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -801,14 +784,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.9"
|
||||
highlight:
|
||||
highlighter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: highlight
|
||||
sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21"
|
||||
name: highlighter
|
||||
sha256: "92180c72b9da8758e1acf39a45aa305a97dcfe2fdc8f3d1d2947c23f2772bfbc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
version: "0.1.1"
|
||||
hive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -829,10 +812,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
sha256: "79d498e6d6761925a34ee5ea8fa6dfef38607781d2fa91e37523474282af55cb"
|
||||
sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.2"
|
||||
version: "0.15.3"
|
||||
html_unescape:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -934,14 +917,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.18.0"
|
||||
isolate:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: isolate
|
||||
sha256: "3554ab10fdeec965d27e0074c913ccb2229887633da080d2b35a6322da14938b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1007,6 +982,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.1"
|
||||
linkify:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: linkify
|
||||
sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1015,6 +998,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
list_counter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: list_counter
|
||||
sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
lists:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1071,15 +1062,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
matrix_link_text:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "9b4d5c28dc602c17757d0eb689aafffe02719f26"
|
||||
resolved-ref: "9b4d5c28dc602c17757d0eb689aafffe02719f26"
|
||||
url: "https://github.com/TheOneWithTheBraid/matrix_link_text.git"
|
||||
source: git
|
||||
version: "1.1.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1152,14 +1134,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: octo_image
|
||||
sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
olm:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1200,14 +1174,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_drawing
|
||||
sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1933,6 +1899,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: ea8d3fc7b2e0f35de38a7465063ecfcf03d8217f7962aa2a6717132cb5d43a79
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.5"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: a5eaa5d19e123ad4f61c3718ca1ed921c4e6254238d9145f82aa214955d9aced
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.5"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "15edc42f7eaa478ce854eaf1fbb9062a899c0e4e56e775dd73b7f4709c97c4ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.5"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
17
pubspec.yaml
17
pubspec.yaml
@ -33,11 +33,14 @@ dependencies:
|
||||
flutter_blurhash: ^0.7.0
|
||||
flutter_cache_manager: ^3.3.0
|
||||
flutter_foreground_task: ^3.10.0
|
||||
flutter_highlighter: ^0.1.1
|
||||
flutter_html: ^3.0.0-beta.1
|
||||
flutter_linkify: ^6.0.0
|
||||
flutter_local_notifications: ^12.0.2
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
flutter_map: ^3.1.0
|
||||
flutter_matrix_html: ^1.1.0
|
||||
flutter_math_fork: ^0.7.1
|
||||
flutter_olm: ^1.2.0
|
||||
flutter_openssl_crypto: ^0.1.0
|
||||
flutter_ringtone_player: ^3.1.1
|
||||
@ -59,7 +62,6 @@ dependencies:
|
||||
latlong2: ^0.8.1
|
||||
matrix: ^0.20.1
|
||||
matrix_homeserver_recommendations: ^0.3.0
|
||||
matrix_link_text: ^1.0.2
|
||||
native_imaging: ^0.1.0
|
||||
package_info_plus: ^4.0.0
|
||||
path_provider: ^2.0.9
|
||||
@ -143,12 +145,6 @@ msix_config:
|
||||
install_certificate: false
|
||||
|
||||
dependency_overrides:
|
||||
# null safety
|
||||
flutter_math_fork: ">=0.7.0"
|
||||
flutter_matrix_html:
|
||||
git:
|
||||
url: https://github.com/Sorunome/flutter_matrix_html.git
|
||||
ref: krille/fix-trying-modify-unmodifiable-list
|
||||
# fake secure storage plugin for Windows
|
||||
# See: https://gitlab.com/gitlab-com/gl-infra/reliability/-/issues/15161
|
||||
flutter_secure_storage_windows:
|
||||
@ -165,11 +161,6 @@ dependency_overrides:
|
||||
git:
|
||||
url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git
|
||||
ref: null-safety
|
||||
# https://github.com/Sorunome/matrix_link_text/pull/2
|
||||
matrix_link_text:
|
||||
git:
|
||||
url: https://github.com/TheOneWithTheBraid/matrix_link_text.git
|
||||
ref: 9b4d5c28dc602c17757d0eb689aafffe02719f26
|
||||
# blocked upgrade of package_info_plus for null safety
|
||||
# https://github.com/creativecreatorormaybenot/wakelock/pull/203
|
||||
wakelock_windows:
|
||||
|
Loading…
Reference in New Issue
Block a user