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: zulip#43
  • Loading branch information
shivanshsharma13 committed Dec 13, 2024
1 parent f7421bf commit b1ac761
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 2 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
12 changes: 12 additions & 0 deletions lib/generated/l10n/zulip_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,12 @@ abstract class ZulipLocalizations {
/// **'Error'**
String get errorDialogTitle;

/// Title for sharing image error dialog.
///
/// In en, this message translates to:
/// **'Error Sharing the Image'**
String get errorShareFailed;

/// Button label for snack bar button that opens a dialog with more details.
///
/// In en, this message translates to:
Expand All @@ -553,6 +559,12 @@ abstract class ZulipLocalizations {
/// **'Copy link'**
String get lightboxCopyLinkTooltip;

/// Tooltip in lightbox for the Share Image action.
///
/// In en, this message translates to:
/// **'Share Image'**
String get lightboxShareImageTooltip;

/// Page title for login page.
///
/// In en, this message translates to:
Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,18 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get errorDialogTitle => 'Error';

@override
String get errorShareFailed => 'Error Sharing the Image';

@override
String get snackBarDetails => 'Details';

@override
String get lightboxCopyLinkTooltip => 'Copy link';

@override
String get lightboxShareImageTooltip => 'Share Image';

@override
String get loginPageTitle => 'Log in';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,18 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
@override
String get errorDialogTitle => 'Error';

@override
String get errorShareFailed => 'Error Sharing the Image';

@override
String get snackBarDetails => 'Details';

@override
String get lightboxCopyLinkTooltip => 'Copy link';

@override
String get lightboxShareImageTooltip => 'Share Image';

@override
String get loginPageTitle => 'Log in';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_ja.dart
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,18 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
@override
String get errorDialogTitle => 'Error';

@override
String get errorShareFailed => 'Error Sharing the Image';

@override
String get snackBarDetails => 'Details';

@override
String get lightboxCopyLinkTooltip => 'Copy link';

@override
String get lightboxShareImageTooltip => 'Share Image';

@override
String get loginPageTitle => 'Log in';

Expand Down
45 changes: 43 additions & 2 deletions lib/widgets/lightbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:share_plus/share_plus.dart';
import 'package:video_player/video_player.dart';

import 'package:http/http.dart' as httpClient;
import '../api/core.dart';
import '../api/model/model.dart';
import '../generated/l10n/zulip_localizations.dart';
Expand Down Expand Up @@ -89,6 +90,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,7 +299,7 @@ class _ImageLightboxPageState extends State<_ImageLightboxPage> {
elevation: elevation,
child: Row(children: [
_CopyLinkButton(url: widget.src),
// TODO(#43): Share image
_ShareButton(url: widget.src),
// TODO(#42): Download image
]),
);
Expand Down
22 changes: 22 additions & 0 deletions test/widgets/lightbox_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,28 @@ 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 b1ac761

Please sign in to comment.