-
Notifications
You must be signed in to change notification settings - Fork 249
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?
content: Handle link previews #1049
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).
lib/widgets/content.dart
Outdated
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;
.
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.
lib/widgets/content.dart
Outdated
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
.
lib/model/content.dart
Outdated
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.
lib/model/content.dart
Outdated
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.
lib/widgets/content.dart
Outdated
|
||
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?
lib/widgets/content.dart
Outdated
// 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.
lib/widgets/content.dart
Outdated
? 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?
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.
Removed this LayoutBuilder
here.
lib/widgets/content.dart
Outdated
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?
lib/widgets/content.dart
Outdated
: 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?
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.
From a quick test, looks like the spacing between different widgets is not consistent (or non-existent in some cases). On Web --markdown-interelement-space-px
is calculated from line-height (which for me becomes 6.72px instead of 5px), so we'd need something similar for zulip-flutter too. However, it'll need a sweep across all the content widgets therefore making it out-of-scope for this PR.
Should 5px bottom margin as a quick fix, suffice for now?
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.
Should 5px bottom margin as a quick fix, suffice for now?
Sure, sounds good
019edde
to
6e37971
Compare
The LinkPreview widget follows the Web styling, like having different layout for larger viewports (> 576), and any other constraints that are empirically present on Web. Fixes: zulip#1016
6e37971
to
70ea927
Compare
Thanks for the review @chrisbobbe! Pushed an update, PTAL. |
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.
Looks good, thanks! Just nits below, and I think #1049 (comment) is still open. Otherwise LGTM; marking for Greg's review.
@@ -148,6 +151,7 @@ class ContentTheme extends ThemeExtension<ContentTheme> { | |||
final Color colorTableCellBorder; | |||
final Color colorTableHeaderBackground; | |||
final Color colorThematicBreak; | |||
final Color colorLink; |
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.
nit: keep in alphabetical order (here and elsewhere in class definition)
@@ -1030,7 +1037,7 @@ class _InlineContentBuilder { | |||
_pushRecognizer(recognizer); | |||
final result = _buildNodes(node.nodes, | |||
// Web has the same color in light and dark mode. |
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.
Let's remove this comment; it would be easy for it to become wrong if the light/dark variants start to be different. Also you've already left a comment with the same meaning on the dark variant.
Fixes: #1016
Screenshots