/// 分享二维码对话框(UI-01 §4.5 分层重构,P3 计划 Task 4 基础)。 /// /// 分层:相册标题 → 二维码(白底突出,暗色主题保持扫码对比度)→ /// 链接文本(可选中)→ 操作行(复制链接为主操作,「在浏览器中打开」为 /// 次操作,经 url_launcher 外链打开)。viewer 链接构造: /// `{VIEWER_BASE}/a/{publicToken}`([ApiConfig.viewerAlbumUrl])。 /// /// 用 [Dialog] 而非 [AlertDialog]:AlertDialog 内容经 IntrinsicWidth /// 测量,而 QrImageView 内部使用 LayoutBuilder(不支持 intrinsic 计算) /// 会抛布局断言,普通 Dialog 无此路径。外链打开经 Provider 注入 /// ([urlLauncherProvider]),widget 测试 override 替身免平台通道。 library; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; import '../core/config.dart'; import '../l10n/app_localizations.dart'; import '../ui/tokens.dart'; /// 外链打开注入点(生产=url_launcher;测试 override)。 typedef UrlLauncher = Future Function(String url); /// Provider:外部浏览器打开 URL(LaunchMode.externalApplication——浏览器 /// 才能承载观众页的全部交互,内置 WebView 不适用)。 final urlLauncherProvider = Provider( (ref) => (url) => launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication), ); /// 观众分享二维码对话框([publicToken] 来自相册详情;[albumTitle] 可选 /// 分层标题,缺省回退通用标题)。 class ShareQrDialog extends ConsumerWidget { const ShareQrDialog({required this.publicToken, super.key, this.albumTitle}); final String publicToken; /// 相册展示名(当前界面语言回退解析后的标题)。 final String? albumTitle; Future _copy(BuildContext context, String url) async { await Clipboard.setData(ClipboardData(text: url)); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(AppLocalizations.of(context).copiedToClipboard), ), ); } Future _openInBrowser( BuildContext context, WidgetRef ref, String url, ) async { final launched = await ref.read(urlLauncherProvider)(url); if (!launched || !context.mounted) return; Navigator.of(context).pop(); } @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context); final theme = Theme.of(context); final url = ApiConfig.viewerAlbumUrl(publicToken); return Dialog( child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 分层 1:相册标题(回退通用标题)。 Text( albumTitle ?? l10n.shareQrTitle, style: theme.textTheme.titleLarge, textAlign: TextAlign.center, ), const SizedBox(height: kSpace4), Text( l10n.shareQrTitle, textAlign: TextAlign.center, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: kSpace16), // 分层 2:二维码(白底突出)。 Center( child: Container( color: Colors.white, padding: const EdgeInsets.all(kSpace8), child: QrImageView( data: url, version: QrVersions.auto, size: 216, backgroundColor: Colors.white, ), ), ), const SizedBox(height: kSpace12), // 分层 3:链接文本(可长按选中)。 SelectableText( url, textAlign: TextAlign.center, style: theme.textTheme.bodySmall, ), const SizedBox(height: kSpace8), Text( l10n.qrHint, textAlign: TextAlign.center, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: kSpace12), // 分层 4:操作行——复制为主操作,浏览器打开为次操作。 Row( children: [ Expanded( child: FilledButton.icon( onPressed: () => _copy(context, url), icon: const Icon(Icons.copy_outlined, size: 18), label: Text(l10n.copyLink), ), ), const SizedBox(width: kSpace8), TextButton( onPressed: () => _openInBrowser(context, ref, url), child: Text(l10n.openInBrowser), ), ], ), ], ), ), ); } }