Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

content: Handle link previews #1049

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

rajveermalviya
Copy link
Collaborator

@rajveermalviya rajveermalviya commented Nov 6, 2024

Fixes: #1016

Screenshots
Web Flutter
Screenshot 2024-12-12 at 23 29 22 Screenshot 2024-12-12 at 23 32 44
Screenshot 2024-12-12 at 23 29 29 Screenshot 2024-12-12 at 23 32 32
Web (Small) Flutter (Small)
Screenshot 2024-12-12 at 23 29 56 Screenshot 2024-12-12 at 23 33 08
Screenshot 2024-12-12 at 23 29 50 Screenshot 2024-12-12 at 23 33 16

@rajveermalviya rajveermalviya force-pushed the pr-content-link-previews branch from abdec10 to 8fba3fe Compare November 18, 2024 21:57
@rajveermalviya rajveermalviya marked this pull request as ready for review November 18, 2024 23:14
@rajveermalviya rajveermalviya added the maintainer review PR ready for review by Zulip maintainers label Nov 18, 2024
@chrisbobbe chrisbobbe self-requested a review December 10, 2024 20:33
@chrisbobbe chrisbobbe self-assigned this Dec 10, 2024
Copy link
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Looks like this needs a rebase, so I'll do a more thorough review after that. But here are some comments from a quick skim (in particular I haven't read the parsing code or checked the UI code against web).

padding: const EdgeInsets.symmetric(horizontal: 5),
child: InsetShadowBox(
bottom: 8,
color: messageListTheme.streamMessageBgDefault,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it'll be wrong for DMs, and (in future) for messages where we highlight the background because of @-mentions in the message (#647).

child: Text(node.title!,
style: TextStyle(
fontSize: 1.2 * kBaseFontSize,
color: const HSLColor.fromAHSL(1, 200, 1, 0.4).toColor()))),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a hard-coded color; does it follow web? It needs either a variable in ContentTheme or this comment:

// Web has the same color in light and dark mode.

(Same for any other hard-coded colors.)

Please also post screenshots in light mode; I see screenshots for dark mode already.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the screenshots.

if (isSmallWidth) {
return Container(
decoration: const BoxDecoration(border:
Border(left: BorderSide(color: Color(0xFFEDEDED), width: 3))),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about hard-coded colors; also, I think we more often use lowercase (so 0xffededed instead of 0xFFEDEDED); here and below.

final messageListTheme = MessageListTheme.of(context);
final isSmallWidth = MediaQuery.sizeOf(context).width <= 576;

final dataContainer = Container(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is dataContainer the best name? I see the Container widget being used…what's the "data" and how does that widget "contain" it?

How about building this method's return value with help from a mutable Widget result variable? So here:

Widget result = Container(/* etc. */);

then below,

result = isSmallWidth
  ? Column(/* etc. */, children: [/* etc. */, result])
  : Row(/* etc. */, children: [/* etc. */, result]);

then result = Container(decoration: /* etc. */, child: result);

and finally return result;.

@rajveermalviya rajveermalviya force-pushed the pr-content-link-previews branch 2 times, most recently from 5b7b364 to 533fa33 Compare December 11, 2024 20:28
@rajveermalviya rajveermalviya force-pushed the pr-content-link-previews branch from 533fa33 to aeabbe6 Compare December 12, 2024 17:45
@rajveermalviya
Copy link
Collaborator Author

Thanks for the initial comments @chrisbobbe! Pushed a new revision, PTAL.

@chrisbobbe
Copy link
Collaborator

What's a good way for me to test this; do I need to set up a dev server? 🙂 I see link previews are disabled on CZO; I've asked on CZO if there's a reason for that: https://chat.zulip.org/#narrow/channel/9-issues/topic/Link.20previews.20for.20Zulip.20URLs/near/2013846

@gnprice
Copy link
Member

gnprice commented Dec 24, 2024

Dev server, or make a test realm on Zulip Cloud.

Copy link
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Comments below.

GestureDetector(
onTap: () => _launchUrl(context, node.hrefUrl),
child: RealmContentNetworkImage(
Uri.parse(node.imageSrcUrl),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just one codepath for URL-parsing this string, instead of splitting by isSmallWidth?

Also, this will throw an error if parsing fails. Instead of doing that, let's use Uri.tryParse instead, similar to what we do in MessageImage.

if (second.nodes.length > 2) return null;

String? title, description;
for (final node in second.nodes) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is both a title and a description, can they appear in either order? If not—if it's always the title first—how about requiring that? For the code structure, instead of a loop, maybe we could do a switch on the length of second.nodes, and in the 1 case we expect either a title or a description, and in the 2 case we expect a title first and then a description.

Comment on lines +1512 to +1516
final first = divElement.nodes.first;
if (first is! dom.Element) return null;
if (first.localName != 'a') return null;
if (first.className != 'message_embed_image') return null;
if (first.nodes.isNotEmpty) return null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can be a bit more compact in some places by using Dart Patterns; try a regex search for if.*case in this file.

@override
Widget build(BuildContext context) {
final messageListTheme = MessageListTheme.of(context);
final isSmallWidth = MediaQuery.sizeOf(context).width <= 576;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

576 is from the web app, right? And some other explicit width and height values below: 500, 80, 115, etc.

We could comment on each one, saying they come from the web app. But actually, I could imagine future design work where we want to tune these numbers to be different from the web app. In that case such comments would become wrong/misleading if we forgot to update them. So maybe best not.

To memoize the fact that they match web, though (so a reader doesn't have to check each one), let's mention it in the commit message.


return Container(
decoration: const BoxDecoration(
border: Border(left: BorderSide(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BorderDirectional(start:, right?

Comment on lines +879 to +881
// TODO(#647) use different color for highlighted messages
// TODO(#681) use different color for DM messages
color: messageListTheme.streamMessageBgDefault,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right; yeah, I forgot we haven't done #681 yet.

I guess this needs one more TODO I hadn't thought of before:

// TODO(#488) use different color for non-message contexts

Probably the desired effect of that TODO will be to guide the implementation toward a color param rather than a param that's about the aspects of a Zulip message.

? titleAndDescription
: LayoutBuilder(
builder: (context, constraints) => ConstrainedBox(
constraints: BoxConstraints(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot about constraints in this code: a Container.constraints, an UnconstrainedBox, a LayoutBuilder, a ConstrainedBox.constraints. I'm not really following it yet; do you think there might be a simpler way to write it?

fit: BoxFit.cover,
width: 80,
height: 80,
alignment: Alignment.center)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't Alignment.center the default; can we leave out this argument?

: LayoutBuilder(
builder: (context, constraints) => ConstrainedBox(
constraints: BoxConstraints(
maxWidth: constraints.maxWidth - 115),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder about dropping this maxWidth - 115 detail, for a few benefits:

  • More of the text can show before it gets clipped
  • Removes the need for LayoutBuilder which isn't great for
    • performance
    • code complexity (e.g. my difficulty in a previous comment)
  • We allow more horizontal space for other paragraph content without issues

Could leave a code comment saying we're not following web in this way.

border: Border(left: BorderSide(
// Web has the same color in light and dark mode.
color: Color(0xffededed), width: 3))),
padding: const EdgeInsets.all(5),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Web also puts 5px bottom margin, a.k.a. --markdown-interelement-space-px, in addition to this. In zulip-flutter do we have something systematic for vertical spacing between block elements, or is each element responsible for adding its own space at the bottom and/or top?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintainer review PR ready for review by Zulip maintainers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Handle message_embed website previews
3 participants