-
Notifications
You must be signed in to change notification settings - Fork 309
Add "Copy to clipboard" button to message action sheet #213
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
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!
Small comments below. Let's also try adding tests of the copyWithPopup
calls, atop those first few commits of #204 (which I think we're likely to merge soon.)
lib/widgets/action_sheet.dart
Outdated
if (rawContent == null) { | ||
return; | ||
} | ||
|
||
if (!messageListContext.mounted) return; |
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: the non-parallelism looks a bit odd:
if (rawContent == null) { | |
return; | |
} | |
if (!messageListContext.mounted) return; | |
if (rawContent == null) return; | |
if (!messageListContext.mounted) return; |
lib/widgets/action_sheet.dart
Outdated
|
||
@override get icon => Icons.copy; | ||
|
||
@override get label => 'Copy to clipboard'; |
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 should be a bit clearer about what it's going to do, I think — there are several things one might imagine being copied.
Perhaps "Copy message text"?
d28aaa8
to
a383afe
Compare
Thanks for the review! Revision pushed, with:
|
a383afe
to
020fb80
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.
Cool! Glad to have tests for this. Various small comments below.
lib/model/binding.dart
Outdated
/// Provides device and operating system information, | ||
/// via package:device_info_plus. | ||
/// | ||
/// This wraps [DeviceInfoPlugin.deviceInfo]. |
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 wraps [DeviceInfoPlugin.deviceInfo]. | |
/// This wraps [device_info_plus.DeviceInfoPlugin.deviceInfo]. |
lib/model/binding.dart
Outdated
/// The user-visible SDK version of the framework. | ||
/// | ||
/// Possible values are defined in: https://developer.android.com/reference/android/os/Build.VERSION_CODES.html |
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.
/// The user-visible SDK version of the framework. | |
/// | |
/// Possible values are defined in: https://developer.android.com/reference/android/os/Build.VERSION_CODES.html | |
```suggestion | |
/// The Android SDK version. | |
/// | |
/// Possible values are defined in: https://developer.android.com/reference/android/os/Build.VERSION_CODES.html |
I wouldn't call it "user-visible" — one never sees this number as an Android user, only as an Android developer.
(I guess the Android docs on the related field https://developer.android.com/reference/android/os/Build.VERSION#SDK calls it that. Looks like that text dates at least to the dawn of Android's public Git history in 2008. It's possible it made sense then, but it hasn't in a long time.)
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 yeah, this was copied from the plugin. Will remove that.
lib/model/binding.dart
Outdated
/// The current operating system version. | ||
/// https://developer.apple.com/documentation/uikit/uidevice/1620043-systemversion |
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.
/// The current operating system version. | |
/// https://developer.apple.com/documentation/uikit/uidevice/1620043-systemversion | |
/// The current operating system version. | |
/// | |
/// See: https://developer.apple.com/documentation/uikit/uidevice/1620043-systemversion |
lib/model/binding.dart
Outdated
|
||
@override | ||
Future<BaseDeviceInfo> deviceInfo() async { | ||
return switch (await device_info_plus.DeviceInfoPlugin().deviceInfo) { |
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.
Doesn't super matter in this instance, but in general I like making await
s conspicuous — they're a key part of the control flow, after all — by putting them at the head of either a whole statement or an initializer:
return switch (await device_info_plus.DeviceInfoPlugin().deviceInfo) { | |
final deviceInfo = await device_info_plus.DeviceInfoPlugin().deviceInfo; | |
return switch (deviceInfo) { |
test/model/binding.dart
Outdated
} | ||
|
||
final _defaultDeviceInfoResult = IosDeviceInfo(systemVersion: '16.0'); |
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 make this a static on the class — that way it can be grouped right next to the rest of the device-info stuff, even while this binding continues to grow more features.
Also let's make it an Android version, because that's what Flutter elsewhere defaults to in tests:
https://api.flutter.dev/flutter/foundation/defaultTargetPlatform.html
test/model/binding.dart
Outdated
/// This returns a list of the arguments to all calls made | ||
/// to `ZulipBinding.instance.deviceInfo()` since the last call to |
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 returns a list of the arguments to all calls made
List<()>
😆
I think I'm actually inclined to leave out this "take" method, and the log it consumes, entirely, though. Because the underlying method doesn't (or anyway isn't meant to) have any side effects, I don't think it's ever going to really be a caller's or a test's business whether the implementation of some feature calls this method, or in particular when or how many times it does so. What is a caller's or test's business is whether the implementation successfully does the appropriate things on different platforms; and the test tests that by having several variants of itself that manipulate deviceInfoResult
differently.
If we later wrap some method that does have side effects (so a test inspecting a call log is appropriate) but takes no arguments, though, I'd be fine with this type for the corresponding "take" method. I'm not sure the formal parallelism with takeLaunchUrlCalls
(and future methods like it) is ultimately helpful, but maybe it will be.
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.
Haha, makes sense.
|
||
dynamic clipboardData; | ||
|
||
Future<Object?> handleMethodCall(MethodCall methodCall) async { |
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 is based on some piece of test code in the Flutter tree, right? Worth a brief comment pointing to there — the code is pretty gnarly-looking with all those dynamic
s, and that's helpful context for understanding it.
test/test_clipboard.dart
Outdated
case 'Clipboard.setData': | ||
clipboardData = methodCall.arguments; | ||
default: | ||
if (methodCall.method.startsWith('Clipboard')) { |
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 (methodCall.method.startsWith('Clipboard')) { | |
if (methodCall.method.startsWith('Clipboard.')) { |
unless you intend to match things like "ClipboardPlus.getFancyData" too.
test/widgets/clipboard_test.dart
Outdated
void main() { | ||
setUp(() async { | ||
TestZulipBinding.ensureInitialized(); | ||
TestWidgetsFlutterBinding.ensureInitialized(); |
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.
void main() { | |
setUp(() async { | |
TestZulipBinding.ensureInitialized(); | |
TestWidgetsFlutterBinding.ensureInitialized(); | |
void main() { | |
TestZulipBinding.ensureInitialized(); | |
TestWidgetsFlutterBinding.ensureInitialized(); | |
setUp(() async { |
That's the usual idiom for these, because they only have effect once.
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( | ||
SystemChannels.platform, | ||
MockClipboard().handleMethodCall, |
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 one is good to do in the per-test setup, though, because it resets the clipboard's data.
020fb80
to
fe719c2
Compare
Thanks for the review! Revision pushed. |
These will also be helpful for testing the upcoming "Copy to clipboard" button, which also requires fetching raw Markdown from the server.
Thanks for the revision! Sorry for the delayed review. All looks good; merging. |
fe719c2
to
9a06553
Compare
This leaves some TODOs in the tests, for testing code that calls into a Flutter plugin. Greg is working on a good way to do that; see #204 (comment).
Empirically, the relevant calls here return Futures that never resolve, which will be fine until we want to stub out the native-code functionality.
Fixes: #132