-
Notifications
You must be signed in to change notification settings - Fork 245
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
base: main
Are you sure you want to change the base?
Conversation
abdec10
to
8fba3fe
Compare
There was a problem hiding this 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, |
There was a problem hiding this comment.
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).
lib/widgets/content.dart
Outdated
child: Text(node.title!, | ||
style: TextStyle( | ||
fontSize: 1.2 * kBaseFontSize, | ||
color: const HSLColor.fromAHSL(1, 200, 1, 0.4).toColor()))), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the screenshots.
lib/widgets/content.dart
Outdated
if (isSmallWidth) { | ||
return Container( | ||
decoration: const BoxDecoration(border: | ||
Border(left: BorderSide(color: Color(0xFFEDEDED), width: 3))), |
There was a problem hiding this comment.
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.
lib/widgets/content.dart
Outdated
final messageListTheme = MessageListTheme.of(context); | ||
final isSmallWidth = MediaQuery.sizeOf(context).width <= 576; | ||
|
||
final dataContainer = Container( |
There was a problem hiding this comment.
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;
.
5b7b364
to
533fa33
Compare
533fa33
to
aeabbe6
Compare
Thanks for the initial comments @chrisbobbe! Pushed a new revision, PTAL. |
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 |
Dev server, or make a test realm on Zulip Cloud. |
There was a problem hiding this 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), |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BorderDirectional(start:
, right?
// TODO(#647) use different color for highlighted messages | ||
// TODO(#681) use different color for DM messages | ||
color: messageListTheme.streamMessageBgDefault, |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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)), |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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?
Fixes: #1016
Screenshots