feat(client): 商品详情页图片支持双击全屏查看

抽出共享全屏查看器 fullscreen_image_viewer.dart(复用扫码页的
InteractiveViewer+PageView+黑底 modal),详情页缩略图双击打开,
扫码公开页改为复用同一组件去重。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-23 20:07:49 +08:00
parent a20645671b
commit 7969bb41e3
3 changed files with 116 additions and 81 deletions
@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../core/config/app_config.dart';
import '../../widgets/fullscreen_image_viewer.dart';
// ── Palette & typography constants ────────────────────────────────────
const _kPaper = Color(0xFFFAF8F5);
@@ -476,12 +477,7 @@ class _HeroGalleryState extends State<_HeroGallery> {
}
void _openFullscreen(int index) {
Navigator.of(context).push(PageRouteBuilder(
opaque: false,
barrierColor: Colors.black87,
pageBuilder: (_, __, ___) => _FullscreenViewer(
urls: widget.imageUrls, initialIndex: index),
));
showFullscreenImages(context, widget.imageUrls, initialIndex: index);
}
}
@@ -534,66 +530,6 @@ class _GlassChip extends StatelessWidget {
}
}
// ── Full-screen image viewer ──────────────────────────────────────────
class _FullscreenViewer extends StatefulWidget {
final List<String> urls;
final int initialIndex;
const _FullscreenViewer({required this.urls, required this.initialIndex});
@override
State<_FullscreenViewer> createState() => _FullscreenViewerState();
}
class _FullscreenViewerState extends State<_FullscreenViewer> {
late PageController _ctrl;
@override
void initState() {
super.initState();
_ctrl = PageController(initialPage: widget.initialIndex);
}
@override
void dispose() { _ctrl.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Scaffold(
backgroundColor: Colors.transparent,
body: Stack(
children: [
PageView.builder(
controller: _ctrl,
itemCount: widget.urls.length,
itemBuilder: (_, i) => Center(
child: InteractiveViewer(
child: Image.network(widget.urls[i], fit: BoxFit.contain,
errorBuilder: (_, __, ___) =>
const Icon(Icons.broken_image, size: 64, color: Colors.white54)),
)),
),
Positioned(
top: 40, right: 16,
child: GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black54, borderRadius: BorderRadius.circular(20)),
child: const Icon(Icons.close, color: Colors.white, size: 20),
),
),
),
],
),
),
);
}
}
// ── Verified Ribbon ───────────────────────────────────────────────────
class _VerifiedRibbon extends StatelessWidget {