Skip to content

Commit d6bf4c8

Browse files
lightbox: Add "share" button in bottom app bar
Add a share button to the lightbox that allows users to share image URLs. The button appears in the bottom app bar with a share icon and tooltip. Test coverage includes verifying the share button's UI elements (icon and tooltip). Fixes: #43
1 parent f7421bf commit d6bf4c8

File tree

3 files changed

+74
-0
lines changed

3 files changed

+74
-0
lines changed

assets/l10n/app_en.arb

+8
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,10 @@
338338
"@errorDialogTitle": {
339339
"description": "Generic title for error dialog."
340340
},
341+
"errorShareFailed": "Error Sharing the Image",
342+
"@errorShareFailed": {
343+
"description": "Title for sharing image error dialog."
344+
},
341345
"snackBarDetails": "Details",
342346
"@snackBarDetails": {
343347
"description": "Button label for snack bar button that opens a dialog with more details."
@@ -346,6 +350,10 @@
346350
"@lightboxCopyLinkTooltip": {
347351
"description": "Tooltip in lightbox for the copy link action."
348352
},
353+
"lightboxShareImageTooltip": "Share Image",
354+
"@lightboxShareImageTooltip": {
355+
"description": "Tooltip in lightbox for the Share Image action."
356+
},
349357
"loginPageTitle": "Log in",
350358
"@loginPageTitle": {
351359
"description": "Page title for login page."

lib/widgets/lightbox.dart

+43
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter/scheduler.dart';
33
import 'package:flutter/services.dart';
4+
import 'package:http/http.dart' as httpClient;
45
import 'package:intl/intl.dart';
6+
import 'package:share_plus/share_plus.dart';
57
import 'package:video_player/video_player.dart';
68

79
import '../api/core.dart';
@@ -89,6 +91,46 @@ class _CopyLinkButton extends StatelessWidget {
8991
}
9092
}
9193

94+
Future<XFile> _downloadImage(Uri url, Map<String, String> headers) async {
95+
final response = await httpClient.get(url, headers: headers);
96+
final bytes = response.bodyBytes;
97+
return XFile.fromData(bytes,
98+
name: url.pathSegments.last,
99+
mimeType: response.headers['content-type']);
100+
}
101+
102+
class _ShareButton extends StatelessWidget {
103+
const _ShareButton({required this.url});
104+
105+
final Uri url;
106+
107+
@override
108+
Widget build(BuildContext context) {
109+
final zulipLocalizations = ZulipLocalizations.of(context);
110+
return IconButton(
111+
tooltip: zulipLocalizations.lightboxShareImageTooltip,
112+
icon: const Icon(Icons.share),
113+
onPressed: () async {
114+
try {
115+
final store = PerAccountStoreWidget.of(context);
116+
final headers = {
117+
if (url.origin == store.account.realmUrl.origin)
118+
...authHeader(email: store.account.email, apiKey: store.account.apiKey),
119+
...userAgentHeader()
120+
};
121+
final xFile = await _downloadImage(url, headers);
122+
await Share.shareXFiles([xFile]);
123+
} catch (error) {
124+
if (!context.mounted) return;
125+
showErrorDialog(
126+
context: context,
127+
title: zulipLocalizations.errorDialogTitle,
128+
message: zulipLocalizations.errorShareFailed);
129+
}
130+
});
131+
}
132+
}
133+
92134
class _LightboxPageLayout extends StatefulWidget {
93135
const _LightboxPageLayout({
94136
required this.routeEntranceAnimation,
@@ -258,6 +300,7 @@ class _ImageLightboxPageState extends State<_ImageLightboxPage> {
258300
elevation: elevation,
259301
child: Row(children: [
260302
_CopyLinkButton(url: widget.src),
303+
_ShareButton(url: widget.src),
261304
// TODO(#43): Share image
262305
// TODO(#42): Download image
263306
]),

test/widgets/lightbox_test.dart

+23
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,29 @@ void main() {
275275
debugNetworkImageHttpClientProvider = null;
276276
});
277277

278+
testWidgets('share button shows correct icon and downloads image', (tester) async {
279+
prepareBoringImageHttpClient();
280+
final message = eg.streamMessage();
281+
await setupPage(tester, message: message, thumbnailUrl: null);
282+
283+
// Verify share icon exists
284+
final shareIcon = find.descendant(
285+
of: find.byType(BottomAppBar),
286+
matching: find.byIcon(Icons.share),
287+
skipOffstage: false);
288+
check(tester.widget<Icon>(shareIcon).icon).equals(Icons.share);
289+
290+
// Verify tooltip
291+
final button = tester.widget<IconButton>(find.ancestor(
292+
of: shareIcon,
293+
matching: find.byType(IconButton)));
294+
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
295+
check(button.tooltip).equals(zulipLocalizations.lightboxShareImageTooltip);
296+
297+
debugNetworkImageHttpClientProvider = null;
298+
});
299+
300+
278301
// TODO test _CopyLinkButton
279302
// TODO test thumbnail gets shown, then gets replaced when main image loads
280303
// TODO test image is scaled down to fit, but not up

0 commit comments

Comments
 (0)