diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/qr_code.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/qr_code.dart new file mode 100644 index 00000000..5c588e82 --- /dev/null +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/qr_code.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:pretty_qr_code/pretty_qr_code.dart'; + +Future showQrCode(BuildContext context, String data) { + return showDialog( + context: context, + builder: (context) { + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Share QR Code', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 20), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + ), + child: PrettyQrView.data( + data: data, + decoration: const PrettyQrDecoration( + image: PrettyQrDecorationImage( + image: AssetImage('assets/icon/icon.png'), + ), + ), + ), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Close'), + ), + ], + ), + ], + ), + ), + ); + }, + ); +} diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart index f3ad0b3f..230ee7d8 100644 --- a/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart @@ -12,9 +12,11 @@ import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; import 'package:weblibre/features/bangs/presentation/widgets/site_search.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; +import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/address_with_suggestions_field.dart'; import 'package:weblibre/features/user/domain/providers.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; +import 'package:weblibre/presentation/widgets/share_tile.dart'; import 'package:weblibre/presentation/widgets/website_feed_tile.dart'; import 'package:weblibre/presentation/widgets/website_title_tile.dart'; import 'package:weblibre/utils/ui_helper.dart' as ui_helper; @@ -180,9 +182,7 @@ class WebPageDialog extends HookConsumerWidget { } }, ), - ListTile( - leading: const Icon(Icons.share), - title: const Text('Share link'), + ShareTile( onTap: () async { await SharePlus.instance.share( ShareParams(uri: url), @@ -192,6 +192,9 @@ class WebPageDialog extends HookConsumerWidget { context.pop(); } }, + onTapQr: () async { + await showQrCode(context, url.toString()); + }, ), ListTile( leading: const Icon(Icons.mobile_screen_share), diff --git a/app/lib/features/geckoview/features/contextmenu/presentation/candidates/share_link.dart b/app/lib/features/geckoview/features/contextmenu/presentation/candidates/share_link.dart index 00055a55..605b6c23 100644 --- a/app/lib/features/geckoview/features/contextmenu/presentation/candidates/share_link.dart +++ b/app/lib/features/geckoview/features/contextmenu/presentation/candidates/share_link.dart @@ -4,7 +4,9 @@ import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nullability/nullability.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart'; import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart'; +import 'package:weblibre/presentation/widgets/share_tile.dart'; class ShareLink extends HookConsumerWidget { final HitResult hitResult; @@ -17,11 +19,11 @@ class ShareLink extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return ListTile( - leading: const Icon(Icons.share), - title: const Text('Share link'), + final link = hitResult.tryGetLink(); + + return ShareTile( onTap: () async { - await hitResult.tryGetLink().mapNotNull( + await link.mapNotNull( (url) => SharePlus.instance.share(ShareParams(uri: url)), ); @@ -29,6 +31,11 @@ class ShareLink extends HookConsumerWidget { context.pop(); } }, + onTapQr: link.mapNotNull( + (url) => () async { + await showQrCode(context, url.toString()); + }, + ), ); } } diff --git a/app/lib/presentation/widgets/share_tile.dart b/app/lib/presentation/widgets/share_tile.dart new file mode 100644 index 00000000..9ba247d4 --- /dev/null +++ b/app/lib/presentation/widgets/share_tile.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +class ShareTile extends StatelessWidget { + final void Function()? onTap; + final void Function()? onTapQr; + + const ShareTile({super.key, this.onTap, this.onTapQr}); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: const Icon(Icons.share), + title: const Text('Share link'), + onTap: onTap, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const VerticalDivider(indent: 4, endIndent: 4), + IconButton(icon: const Icon(Icons.qr_code), onPressed: onTapQr), + ], + ), + ); + } +} diff --git a/app/pubspec.yaml b/app/pubspec.yaml index eb138915..30faec9e 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -47,6 +47,7 @@ dependencies: package_info_plus: ^8.3.0 path: ^1.9.1 path_provider: ^2.1.5 + pretty_qr_code: ^3.4.0 riverpod: ^2.6.1 riverpod_annotation: ^2.6.1 rss_dart: ^1.0.12