fix up url embeds

This commit is contained in:
Henry Hiles 2026-05-19 10:07:15 -04:00
commit fee12cb94d
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
8 changed files with 293 additions and 275 deletions

View file

@ -26,15 +26,15 @@ class UrlPreviewController extends AsyncNotifier<OpenGraphData?> {
);
if (response.statusCode == 200) {
final decodedValue = json.decode(response.body) as Map?;
if (decodedValue?.isNotEmpty == true) return null;
final decodedValue = json.decode(response.body);
if (decodedValue is! Map<String, dynamic>) return null;
final mxc = decodedValue!["og:image"];
final mxc = decodedValue["og:image"];
final image = mxc == null
? null
: Uri.tryParse(mxc)?.mxcToHttps(homeserver);
return OpenGraphData.fromJson({...decodedValue, "og:image": image});
return OpenGraphData.fromJson(decodedValue).copyWith(imageUrl: image);
}
}
}

View file

@ -7,11 +7,11 @@ abstract class OpenGraphData with _$OpenGraphData {
const factory OpenGraphData({
@JsonKey(name: "og:title") required String? title,
@JsonKey(name: "og:description") required String? description,
@JsonKey(name: "og:image") required String? imageUrl,
@JsonKey(name: "og:image") required Uri? imageUrl,
@JsonKey(name: "og:image:width") required double? width,
@JsonKey(name: "og:image:height") required double? height,
}) = _OpenGraphData;
factory OpenGraphData.fromJson(Map<String, Object?> json) =>
factory OpenGraphData.fromJson(Map<String, dynamic> json) =>
_$OpenGraphDataFromJson(json);
}

View file

@ -23,6 +23,7 @@ class Html extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) => HtmlWidget(
html,
buildAsync: false,
textStyle: textStyle,
customWidgetBuilder: (element) {
if (element.attributes.keys.contains("data-mx-profile-fallback")) {

View file

@ -1,8 +1,10 @@
import "package:collection/collection.dart";
import "package:cross_cache/cross_cache.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter_blurhash/flutter_blurhash.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:linkify/linkify.dart";
import "package:nexus/controllers/client_state_controller.dart";
import "package:nexus/controllers/cross_cache_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
@ -59,17 +61,16 @@ class RenderEvent extends ConsumerWidget {
children: getEventOptions!(event).toList(),
);
return GestureDetector(
onSecondaryTapUp: contextMenuCallback,
onLongPressStart: contextMenuCallback,
child: switch (event.content) {
final child = switch (event.content) {
MessageContent() => Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
isGrouped || textOnly
? SizedBox(width: 40)
: MessageAvatar(event, height: 40),
if (!textOnly)
if (isGrouped)
SizedBox(width: 40)
else
MessageAvatar(event, height: 40),
Expanded(
child: Column(
spacing: 4,
@ -79,21 +80,26 @@ class RenderEvent extends ConsumerWidget {
Row(
spacing: 4,
children: [
MessageDisplayname(
Flexible(
child: MessageDisplayname(
event,
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Flexible(child: timestamp),
],
),
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8)),
borderRadius: textOnly
? BorderRadius.zero
: BorderRadius.all(Radius.circular(8)),
child: Container(
padding: EdgeInsets.symmetric(
vertical: 8,
horizontal: 12,
),
decoration: BoxDecoration(
padding: textOnly
? EdgeInsets.zero
: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: textOnly
? null
: BoxDecoration(
color:
ref.watch(
ClientStateController.provider.select(
@ -122,6 +128,11 @@ class RenderEvent extends ConsumerWidget {
:final formattedBody,
:final format,
) ||
NoticeMessageContent(
:final body,
:final formattedBody,
:final format,
) ||
ImageMessageContent(
:final body,
:final formattedBody,
@ -156,9 +167,8 @@ class RenderEvent extends ConsumerWidget {
: Linkify(
text: body,
maxLines: maxLines,
options: LinkifyOptions(
humanize: false,
),
overflow: TextOverflow.ellipsis,
options: LinkifyOptions(humanize: false),
onOpen: (link) => ref
.watch(LaunchHelper.provider)
.launchUrl(Uri.parse(link.url)),
@ -200,8 +210,7 @@ class RenderEvent extends ConsumerWidget {
loadingProgress == null
? child
: switch (info?.blurHash) {
final blurHash? =>
SizedBox(
final blurHash? => SizedBox(
width:
info?.width ??
info?.height ??
@ -236,25 +245,19 @@ class RenderEvent extends ConsumerWidget {
style: errorStyle,
),
},
if (event.lastEditRowId != null)
if (event.lastEditRowId != null && !textOnly)
Text(
"(edited)",
style: theme.textTheme.labelSmall,
),
if (RegExp(
r'''https?://[^\s"'<>]+''',
).allMatches(body).firstOrNull?.group(0)
case final link?)
LinkPreview(link),
if (linkify(body).firstWhereOrNull(
(element) => element is UrlElement,
)
case final UrlElement link?)
LinkPreview(link.url),
],
),
_ =>
textOnly
? Text(
"Unknown message type",
style: errorStyle,
)
: SizedBox.shrink(),
_ => Text("Unknown message type", style: errorStyle),
},
],
),
@ -280,11 +283,17 @@ class RenderEvent extends ConsumerWidget {
Text("changed the room avatar"),
],
),
_ =>
textOnly
_ => null,
};
return GestureDetector(
onSecondaryTapUp: contextMenuCallback,
onLongPressStart: contextMenuCallback,
child: child == null
? textOnly
? Text("Unknown event type", style: errorStyle)
: SizedBox.shrink(),
},
: SizedBox.shrink()
: Padding(padding: EdgeInsets.symmetric(vertical: 8), child: child),
);
}
}

View file

@ -325,9 +325,7 @@ class RoomChat extends HookConsumerWidget {
SuperSliverList.builder(
listController: listController.value,
itemCount: value.length,
itemBuilder: (_, index) => Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: EventWrapper(
itemBuilder: (_, index) => EventWrapper(
value[index],
RenderEvent(
value[index],
@ -348,7 +346,6 @@ class RoomChat extends HookConsumerWidget {
isFlashing: false,
),
),
),
],
),
AsyncLoading() => Loading(),

View file

@ -17,44 +17,48 @@ class LinkPreview extends ConsumerWidget {
.betterWhen(
data: (preview) => preview == null
? SizedBox.shrink()
: InkWell(
onTap: () =>
ref.watch(LaunchHelper.provider).launchUrl(Uri.parse(link)),
: ConstrainedBox(
constraints: BoxConstraints.loose(Size.fromWidth(400)),
child: InkWell(
onTap: () => ref
.watch(LaunchHelper.provider)
.launchUrl(Uri.parse(link)),
child: Card(
child: Padding(
padding: EdgeInsetsGeometry.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [
if (preview.title != null)
Text(
preview.title!,
style: Theme.of(context).textTheme.labelLarge,
style: Theme.of(context).textTheme.titleLarge,
),
if (preview.description != null)
if (preview.description != null) ...[
Text(preview.description!),
SizedBox(height: 4),
],
if (preview.imageUrl != null)
Image(
ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
child: Image(
errorBuilder: (_, _, _) => SizedBox.shrink(),
width: preview.width,
height: preview.height,
image: CachedNetworkImage(
preview.imageUrl!,
preview.imageUrl.toString(),
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
fit: BoxFit.cover,
fit: BoxFit.fitWidth,
),
),
],
),
// text: link,
// backgroundColor: isSentByMe
// ? colorScheme.inversePrimary
// : colorScheme.surfaceContainerLow,
// outsidePadding: EdgeInsets.only(top: 4),
// insidePadding: EdgeInsets.symmetric(
// vertical: 8,
// horizontal: 16,
// ),
// linkPreviewData: preview,
// onLinkPreviewDataFetched: (_) => null,
),
),
),
),
);

View file

@ -720,12 +720,13 @@ packages:
source: hosted
version: "3.0.2"
linkify:
dependency: transitive
dependency: "direct overridden"
description:
name: linkify
sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832"
url: "https://pub.dev"
source: hosted
path: "."
ref: "fix/consecutive-periods-loose-url"
resolved-ref: e990021f30b8535b462d41a39f37019045ae55f4
url: "https://github.com/appelladev/linkify"
source: git
version: "5.0.0"
lints:
dependency: transitive

View file

@ -11,6 +11,12 @@ flutter:
environment:
sdk: "3.11.4"
dependency_overrides:
linkify:
git:
url: https://github.com/appelladev/linkify
ref: fix/consecutive-periods-loose-url
dependencies:
flutter:
sdk: flutter