diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index cebffbe8ea..584db9c688 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -124,6 +124,10 @@ "message": {"type": "String", "example": "Invalid format"} } }, + "errorSharingFailed": "Sharing failed", + "@errorSharingFailed": { + "description": "Error message when sharing a message failed." + }, "successLinkCopied": "Link copied", "@successLinkCopied": { "description": "Success message after copy link action completed." diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index af445b20e7..29ac03abdb 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -76,19 +76,20 @@ class ShareButton extends MessageActionSheetMenuItemButton { @override get onPressed => (BuildContext context) async { // Close the message action sheet; we're about to show the share - // sheet. (We could do this after the sharing Future settles, but - // on iOS I get impatient with how slowly our action sheet - // dismisses in that case.) + // sheet. (We could do this after the sharing Future settles + // with [ShareResultStatus.success], but on iOS I get impatient with + // how slowly our action sheet dismisses in that case.) // TODO(#24): Fix iOS bug where this call causes the keyboard to // reopen (if it was open at the time of this // `showMessageActionSheet` call) and cover a large part of the // share sheet. Navigator.of(context).pop(); + final zulipLocalizations = ZulipLocalizations.of(messageListContext); final rawContent = await fetchRawContentWithFeedback( context: messageListContext, messageId: message.id, - errorDialogTitle: 'Sharing failed', + errorDialogTitle: zulipLocalizations.errorSharingFailed, ); if (rawContent == null) return; @@ -100,7 +101,19 @@ class ShareButton extends MessageActionSheetMenuItemButton { // https://pub.dev/packages/share_plus#ipad // Perhaps a wart in the API; discussion: // https://github.com/zulip/zulip-flutter/pull/12#discussion_r1130146231 - await Share.shareWithResult(rawContent); + final result = await Share.shareWithResult(rawContent); + + switch (result.status) { + // The plugin isn't very helpful: "The status can not be determined". + // Until we learn otherwise, assume something wrong happened. + case ShareResultStatus.unavailable: + if (!messageListContext.mounted) return; + await showErrorDialog(context: messageListContext, + title: zulipLocalizations.errorSharingFailed); + case ShareResultStatus.success: + case ShareResultStatus.dismissed: + // nothing to do + } }; } diff --git a/test/test_share_plus.dart b/test/test_share_plus.dart index 3855a9b8e7..ecd63dfe88 100644 --- a/test/test_share_plus.dart +++ b/test/test_share_plus.dart @@ -1,8 +1,12 @@ import 'package:flutter/services.dart'; +import 'package:share_plus/share_plus.dart'; class MockSharePlus { MockSharePlus(); + /// The mock [ShareResult.raw] that `shareWithResult` should give. + String resultString = 'some-success-response'; + /// The last string that `shareWithResult` was called with. String? sharedString; @@ -13,7 +17,7 @@ class MockSharePlus { // `arguments`'s type; logging runtimeType gives _Map. final arguments = methodCall.arguments as Map; sharedString = arguments['text'] as String; - return 'some-success-response'; + return resultString; default: throw UnimplementedError(); } diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index 9af0bcca68..903e0cbbc0 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -116,7 +116,7 @@ void main() { await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e } - testWidgets('success', (WidgetTester tester) async { + testWidgets('request succeeds; sharing succeeds', (WidgetTester tester) async { final mockSharePlus = setupMockSharePlus(); final message = eg.streamMessage(); await setupToMessageActionSheet(tester, message: message, narrow: TopicNarrow.ofMessage(message)); @@ -128,6 +128,22 @@ void main() { check(mockSharePlus.sharedString).equals('Hello world'); }); + testWidgets('request succeeds; sharing fails', (WidgetTester tester) async { + final mockSharePlus = setupMockSharePlus(); + final message = eg.streamMessage(); + await setupToMessageActionSheet(tester, message: message, narrow: TopicNarrow.ofMessage(message)); + final store = await TestZulipBinding.instance.globalStore.perAccount(eg.selfAccount.id); + + prepareRawContentResponseSuccess(store, message: message, rawContent: 'Hello world'); + mockSharePlus.resultString = 'dev.fluttercommunity.plus/share/unavailable'; + await tapShareButton(tester); + await tester.pump(Duration.zero); + check(mockSharePlus.sharedString).equals('Hello world'); + await tester.pump(); + await tester.tap(find.byWidget(checkErrorDialog(tester, + expectedTitle: 'Sharing failed'))); + }); + testWidgets('request has an error', (WidgetTester tester) async { final mockSharePlus = setupMockSharePlus(); final message = eg.streamMessage();