| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- import 'dart:typed_data';
- import 'package:flutter/material.dart';
- /// 图片预览弹窗,支持捏合缩放。
- ///
- /// 以半透明蒙版覆盖在当前页面上方,内部自动加载图片,
- /// 加载期间显示 loading 动画,加载失败显示错误提示。
- ///
- /// 使用方式:
- /// ```dart
- /// AttachmentPreview.show(context,
- /// loader: api.downloadAttachment(id),
- /// fileName: 'photo.jpg',
- /// loadingText: '加载中…',
- /// );
- /// ```
- class AttachmentPreview {
- AttachmentPreview._();
- /// 显示图片预览弹窗。
- static Future<void> show(
- BuildContext context, {
- required Future<Uint8List?> loader,
- required String fileName,
- required String loadingText,
- }) {
- return showGeneralDialog(
- context: context,
- barrierDismissible: true,
- barrierLabel: 'Close',
- barrierColor: Colors.black87,
- transitionDuration: const Duration(milliseconds: 250),
- pageBuilder: (context, animation, secondaryAnimation) {
- return FadeTransition(
- opacity: animation,
- child: Material(
- type: MaterialType.transparency,
- child: _PreviewContent(
- loader: loader,
- fileName: fileName,
- loadingText: loadingText,
- ),
- ),
- );
- },
- );
- }
- }
- class _PreviewContent extends StatefulWidget {
- final Future<Uint8List?> loader;
- final String fileName;
- final String loadingText;
- const _PreviewContent({
- required this.loader,
- required this.fileName,
- required this.loadingText,
- });
- @override
- State<_PreviewContent> createState() => _PreviewContentState();
- }
- class _PreviewContentState extends State<_PreviewContent> {
- Uint8List? _bytes;
- bool _loading = true;
- String? _error;
- @override
- void initState() {
- super.initState();
- _load();
- }
- Future<void> _load() async {
- try {
- final bytes = await widget.loader;
- if (!mounted) return;
- if (bytes == null) {
- setState(() { _error = 'Download failed'; _loading = false; });
- return;
- }
- setState(() { _bytes = bytes; _loading = false; });
- } catch (_) {
- if (!mounted) return;
- setState(() { _error = 'Open failed'; _loading = false; });
- }
- }
- @override
- Widget build(BuildContext context) {
- return SafeArea(
- child: Stack(
- children: [
- Center(child: _buildBody()),
- Positioned(
- top: 8,
- right: 8,
- child: IconButton(
- icon: const Icon(Icons.close, color: Colors.white70, size: 28),
- onPressed: () => Navigator.of(context).pop(),
- ),
- ),
- Positioned(
- top: 16,
- left: 16,
- right: 56,
- child: Text(
- widget.fileName,
- style: const TextStyle(color: Colors.white, fontSize: 15),
- overflow: TextOverflow.ellipsis,
- ),
- ),
- ],
- ),
- );
- }
- Widget _buildBody() {
- if (_loading) {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const CircularProgressIndicator(color: Colors.white),
- const SizedBox(height: 16),
- Text(widget.loadingText,
- style: const TextStyle(color: Colors.white70, fontSize: 14)),
- ],
- );
- }
- if (_error != null || _bytes == null) {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Icon(Icons.broken_image_outlined, color: Colors.white54, size: 48),
- const SizedBox(height: 12),
- Text(_error ?? 'Unknown error',
- style: const TextStyle(color: Colors.white70, fontSize: 14)),
- ],
- );
- }
- return InteractiveViewer(
- minScale: 0.5,
- maxScale: 4.0,
- child: Image.memory(_bytes!),
- );
- }
- }
|