Skip to content

Commit

Permalink
content: Implement embed video preview
Browse files Browse the repository at this point in the history
Partially implements zulip#356, provides video thumbnail previews
for embedded external videos (Youtube & Vimeo).
  • Loading branch information
rajveermalviya committed May 2, 2024
1 parent 006add4 commit c963b84
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 7 deletions.
35 changes: 28 additions & 7 deletions lib/widgets/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,7 @@ class BlockContentList extends StatelessWidget {
]),
style: errorStyle);
} else if (node is EmbedVideoNode) {
return Text.rich(
TextSpan(children: [
const TextSpan(text: "(unimplemented:", style: errorStyle),
TextSpan(text: node.debugHtmlText, style: errorCodeStyle),
const TextSpan(text: ")", style: errorStyle),
]),
style: errorStyle);
return MessageEmbedVideo(node: node);
} else if (node is UnimplementedBlockContentNode) {
return Text.rich(_errorUnimplemented(node));
} else {
Expand Down Expand Up @@ -403,6 +397,33 @@ class MessageImage extends StatelessWidget {
}
}

class MessageEmbedVideo extends StatelessWidget {
const MessageEmbedVideo({super.key, required this.node});

final EmbedVideoNode node;

@override
Widget build(BuildContext context) {
final store = PerAccountStoreWidget.of(context);
final previewImageSrcUrl = store.tryResolveUrl(node.previewImageSrcUrl);

return MessageMediaContainer(
onTap: () => _launchUrl(context, node.hrefUrl),
child: Stack(
alignment: Alignment.center,
children: [
if (previewImageSrcUrl != null)
RealmContentNetworkImage(
previewImageSrcUrl,
filterQuality: FilterQuality.medium),
const Icon(
Icons.play_arrow_rounded,
color: Colors.white,
size: 32),
]));
}
}

class MessageMediaContainer extends StatelessWidget {
const MessageMediaContainer({
super.key,
Expand Down
57 changes: 57 additions & 0 deletions test/widgets/content_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,63 @@ void main() {
});
});

group("MessageEmbedVideo", () {
Future<void> prepareContent(WidgetTester tester, String html) async {
addTearDown(testBinding.reset);
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
prepareBoringImageHttpClient();

await tester.pumpWidget(GlobalStoreWidget(child: MaterialApp(
home: PerAccountStoreWidget(accountId: eg.selfAccount.id,
child: MessageContent(
message: eg.streamMessage(content: html),
content: parseContent(html))))));
await tester.pump(); // global store
await tester.pump(); // per-account store
debugNetworkImageHttpClientProvider = null;
}

testWidgets('video preview for youtube embed', (tester) async {
const example = ContentExample.videoEmbedYoutube;
await prepareContent(tester, example.html);

final expectedVideo = example.expectedNodes[1] as EmbedVideoNode;
final expectedResolvedPreviewUrl = eg
.store()
.tryResolveUrl(expectedVideo.previewImageSrcUrl)!;
final image = tester.widget<RealmContentNetworkImage>(
find.byType(RealmContentNetworkImage));
check(image.src)
.equals(expectedResolvedPreviewUrl);

final expectedLaunchUrl = expectedVideo.hrefUrl;
await tester.tap(find.byIcon(Icons.play_arrow_rounded));
check(testBinding.takeLaunchUrlCalls())
.single.equals((url: Uri.parse(expectedLaunchUrl), mode: LaunchMode.platformDefault));
});

testWidgets('video preview for vimeo embed', (tester) async {
const example = ContentExample.videoEmbedVimeo;
await prepareContent(tester, example.html);

final expectedTitle = (((example.expectedNodes[0] as ParagraphNode).nodes[0] as LinkNode).nodes[0] as TextNode).text;
await tester.ensureVisible(find.text(expectedTitle));

final expectedVideo = example.expectedNodes[1] as EmbedVideoNode;
final expectedResolvedUrl = eg.store().tryResolveUrl(expectedVideo.previewImageSrcUrl)!;
final image = tester.widget<RealmContentNetworkImage>(
find.byType(RealmContentNetworkImage));
check(image.src)
.equals(expectedResolvedUrl);

final expectedLaunchUrl = expectedVideo.hrefUrl;
await tester.tap(find.byIcon(Icons.play_arrow_rounded));
check(testBinding.takeLaunchUrlCalls())
.single.equals((url: Uri.parse(expectedLaunchUrl), mode: LaunchMode.platformDefault));
});
});


group("CodeBlock", () {
testContentSmoke(ContentExample.codeBlockPlain);
testContentSmoke(ContentExample.codeBlockHighlightedShort);
Expand Down

0 comments on commit c963b84

Please sign in to comment.