-
Notifications
You must be signed in to change notification settings - Fork 255
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
Touchable links in messages #204
Conversation
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, this direction looks good! One small comment below.
lib/widgets/content.dart
Outdated
try { | ||
url = store.account.realmUrl.resolve(urlString); | ||
} on FormatException { | ||
debugPrint('link in content failed to parse: $urlString'); // TODO(log) |
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.
Would also be good to give user feedback here, I think, and perhaps also in the case (below this) where parsing succeeds but launching fails for whatever reason.
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.
Sure, good thought.
Thanks for the review!
Just pushed an updated version. Still a draft, but:
The main remaining yak to shave is testing. This requires some sort of mock for the part where we go and invoke Also open is giving the user feedback when parsing or launching fails (as discussed above at #204 (comment) ), and finding or filing an upstream issue about getting the normal open-a-browser interaction on Android, where the system gives the user a choice of browser. |
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, looking forward to this!
lib/widgets/content.dart
Outdated
child: InlineContent( | ||
// If an @-mention is inside a link, let the @-mention override it. | ||
recognizer: null, // TODO make @-mentions tappable, for info on user | ||
// An @-mention can't have an embedded link, can it? // TODO fact-check that. |
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.
// An @-mention can't have an embedded link, can it? // TODO fact-check that.
Wouldn't this require a user's full_name
to contain Markdown inline-link syntax? I'm not sure if servers would parse such syntax in an @-mention, but if they do, I think we shouldn't give it an open-URL handler. An @-mention span shouldn't have a behavior where 0.0001% of the time it takes you to some website of a user's choosing, instead of opening the user-profile view in the app.
OK, and here it is with tests. Came out pretty nicely, I think, using the "binding" pattern. |
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.
Nice!
@@ -172,12 +171,13 @@ class _MessageListState extends State<MessageList> { | |||
// TODO: Offer `ScrollViewKeyboardDismissBehavior.interactive` (or | |||
// similar) if that is ever offered: | |||
// https://github.com/flutter/flutter/issues/57609#issuecomment-1355340849 | |||
keyboardDismissBehavior: Platform.isIOS | |||
keyboardDismissBehavior: switch (Theme.of(context).platform) { |
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.
platform: Use Flutter "target" platform, not actual Platform
Neat!
|
||
/// The value that `ZulipBinding.instance.launchUrl()` should return. | ||
/// | ||
/// See also [takeLaunchUrlCalls]. | ||
bool launchUrlResult = true; | ||
|
||
/// Consume the log of calls made to `ZulipBinding.instance.launchUrl()`. | ||
/// | ||
/// This returns a list of the arguments to all calls made | ||
/// to `ZulipBinding.instance.launchUrl()` since the last call to | ||
/// either this method or [reset]. | ||
/// | ||
/// See also [launchUrlResult]. | ||
List<({Uri url, url_launcher.LaunchMode mode})> takeLaunchUrlCalls() { | ||
final result = _launchUrlCalls; | ||
_launchUrlCalls = null; | ||
return result ?? []; | ||
} | ||
List<({Uri url, url_launcher.LaunchMode mode})>? _launchUrlCalls; | ||
|
||
@override | ||
Future<bool> launchUrl( | ||
Uri url, { | ||
url_launcher.LaunchMode mode = url_launcher.LaunchMode.platformDefault, | ||
}) async { | ||
(_launchUrlCalls ??= []).add((url: url, mode: mode)); | ||
return launchUrlResult; | ||
} |
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.
Cool! One obstacle that's been in the way of testing the compose box's upload-and-attach buttons is that they use Flutter plugins. This pattern seems helpful for addressing that, too.
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.
Yeah, definitely.
If we did end up splitting the bindings into several mixin classes for code organization, I'm imagining the split would look like
- data
- notifications
- miscellaneous plugins that have one or two relevant methods each, where the plugins behind those compose-box buttons would be a prime example (and
url_launcher
is another prime example).
OK, now ready! PTAL. As anticipated, I dropped the last few commits: |
Thanks, this looks good! Here are two things I noticed in testing. Not that they need to block merging though, but what do you think:
|
Thanks for the review! I'll merge.
Hmm, wacky. Please go ahead and file that as a bug after this is merged. A variation on that scenario would be when the paragraph wraps over multiple lines, and the last line is short and ends with a link. I'd guess the same bug would reproduce there. It sounds like the last TextSpan's
Interesting. That zulip-mobile behavior seems nicer for the user, when it works. That logic is pretty bad, though; for example it would match image-looking URLs on external sites (for which the lightbox seems inappropriate), and conversely it's not clear where the list of extensions comes from and whether it might miss some perfectly good uploaded images for which the server cheerfully embeds a preview and the lightbox would work fine. I think that can be a follow-up issue too. |
This makes it possible to test this sort of logic. See docs for [defaultTargetPlatform]: https://api.flutter.dev/flutter/foundation/defaultTargetPlatform.html
We'll need some more bindings in this spirit. It's not clear there'll be enough of them that we need to split them into mixins across multiple files, like Flutter upstream does, so we'll try just putting them all in a single bindings class. Start by giving that one class a more generic name to fit.
This will give us a convenient way to manage "context" information down through the recursion.
This doesn't yet do much, but will.
At this stage the recognizer is always null and so this doesn't do anything. But it provides us the infrastructure with which to start recognizing taps on links, zulip#71.
This is a lot better than the default, though still not ideal. Comments describe a couple of upstream bugs that we should file or comment on.
Makes sense; I've just filed those issues: |
Fixes #71.
Stacked atop #203.
This is a draft. The branch needs some cleanup, but more fundamentally:
TextSpan
respond to two different gestures). I might end up separating the long-press into a separate PR from the main functionality of tapping to follow the link.But from a user perspective, this version already works nicely. And between the two points above, plus just iterating on refactorings to get this code to be as clean as it is (at the end of the branch) in this version, it's been a bit over a week since I sat down and wrote up a version that works nicely from a user perspective. So I figured it'd be good to share this branch as a demo, and a preview of what the resulting code will look like.
I also have a draft branch that does much of the yak shave to be able to write tests for content parsing. I might send a PR first that lays that groundwork, and also adds tests for some of the existing content-parsing code, and then return to this after that.