Skip to content

Commit

Permalink
lightbox: Add "share" button in bottom app bar
Browse files Browse the repository at this point in the history
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
  • Loading branch information
shivanshsharma13 committed Dec 12, 2024
1 parent f7421bf commit d6bf4c8
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 0 deletions.
8 changes: 8 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@
"@errorDialogTitle": {
"description": "Generic title for error dialog."
},
"errorShareFailed": "Error Sharing the Image",
"@errorShareFailed": {
"description": "Title for sharing image error dialog."
},
"snackBarDetails": "Details",
"@snackBarDetails": {
"description": "Button label for snack bar button that opens a dialog with more details."
Expand All @@ -346,6 +350,10 @@
"@lightboxCopyLinkTooltip": {
"description": "Tooltip in lightbox for the copy link action."
},
"lightboxShareImageTooltip": "Share Image",
"@lightboxShareImageTooltip": {
"description": "Tooltip in lightbox for the Share Image action."
},
"loginPageTitle": "Log in",
"@loginPageTitle": {
"description": "Page title for login page."
Expand Down
43 changes: 43 additions & 0 deletions lib/widgets/lightbox.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as httpClient;
import 'package:intl/intl.dart';
import 'package:share_plus/share_plus.dart';
import 'package:video_player/video_player.dart';

import '../api/core.dart';
Expand Down Expand Up @@ -89,6 +91,46 @@ class _CopyLinkButton extends StatelessWidget {
}
}

Future<XFile> _downloadImage(Uri url, Map<String, String> headers) async {
final response = await httpClient.get(url, headers: headers);
final bytes = response.bodyBytes;
return XFile.fromData(bytes,
name: url.pathSegments.last,
mimeType: response.headers['content-type']);
}

class _ShareButton extends StatelessWidget {
const _ShareButton({required this.url});

final Uri url;

@override
Widget build(BuildContext context) {
final zulipLocalizations = ZulipLocalizations.of(context);
return IconButton(
tooltip: zulipLocalizations.lightboxShareImageTooltip,
icon: const Icon(Icons.share),
onPressed: () async {
try {
final store = PerAccountStoreWidget.of(context);
final headers = {
if (url.origin == store.account.realmUrl.origin)
...authHeader(email: store.account.email, apiKey: store.account.apiKey),
...userAgentHeader()
};
final xFile = await _downloadImage(url, headers);
await Share.shareXFiles([xFile]);
} catch (error) {
if (!context.mounted) return;
showErrorDialog(
context: context,
title: zulipLocalizations.errorDialogTitle,
message: zulipLocalizations.errorShareFailed);
}
});
}
}

class _LightboxPageLayout extends StatefulWidget {
const _LightboxPageLayout({
required this.routeEntranceAnimation,
Expand Down Expand Up @@ -258,6 +300,7 @@ class _ImageLightboxPageState extends State<_ImageLightboxPage> {
elevation: elevation,
child: Row(children: [
_CopyLinkButton(url: widget.src),
_ShareButton(url: widget.src),
// TODO(#43): Share image
// TODO(#42): Download image
]),
Expand Down
23 changes: 23 additions & 0 deletions test/widgets/lightbox_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,29 @@ void main() {
debugNetworkImageHttpClientProvider = null;
});

testWidgets('share button shows correct icon and downloads image', (tester) async {
prepareBoringImageHttpClient();
final message = eg.streamMessage();
await setupPage(tester, message: message, thumbnailUrl: null);

// Verify share icon exists
final shareIcon = find.descendant(
of: find.byType(BottomAppBar),
matching: find.byIcon(Icons.share),
skipOffstage: false);
check(tester.widget<Icon>(shareIcon).icon).equals(Icons.share);

// Verify tooltip
final button = tester.widget<IconButton>(find.ancestor(
of: shareIcon,
matching: find.byType(IconButton)));
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
check(button.tooltip).equals(zulipLocalizations.lightboxShareImageTooltip);

debugNetworkImageHttpClientProvider = null;
});


// TODO test _CopyLinkButton
// TODO test thumbnail gets shown, then gets replaced when main image loads
// TODO test image is scaled down to fit, but not up
Expand Down

0 comments on commit d6bf4c8

Please sign in to comment.