import 'package:flutter/material.dart'; class AttachmentGalleryScreen extends StatefulWidget { final List images; final int initialIndex; const AttachmentGalleryScreen({ super.key, required this.images, required this.initialIndex, }); @override State createState() => _AttachmentGalleryScreenState(); } class _AttachmentGalleryScreenState extends State { late PageController _pageController; int _currentIndex = 0; @override void initState() { super.initState(); _currentIndex = widget.initialIndex; _pageController = PageController(initialPage: widget.initialIndex); } @override void dispose() { _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, appBar: AppBar( backgroundColor: Colors.black, iconTheme: const IconThemeData(color: Colors.white), title: Text( '${_currentIndex + 1} / ${widget.images.length}', style: const TextStyle(color: Colors.white), ), ), body: PageView.builder( controller: _pageController, onPageChanged: (index) { setState(() { _currentIndex = index; }); }, itemCount: widget.images.length, itemBuilder: (context, index) { return _ZoomableImage(image: widget.images[index]); }, ), ); } } class _ZoomableImage extends StatefulWidget { final ImageProvider image; const _ZoomableImage({required this.image}); @override State<_ZoomableImage> createState() => _ZoomableImageState(); } class _ZoomableImageState extends State<_ZoomableImage> with SingleTickerProviderStateMixin { final TransformationController _transformationController = TransformationController(); late AnimationController _animationController; Animation? _animation; TapDownDetails? _doubleTapDetails; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 200), )..addListener(() { if (_animation != null) { _transformationController.value = _animation!.value; } }); } @override void dispose() { _animationController.dispose(); _transformationController.dispose(); super.dispose(); } void _handleDoubleTap() { if (_doubleTapDetails == null) return; final position = _doubleTapDetails!.localPosition; final matrix = _transformationController.value; final scale = matrix.getMaxScaleOnAxis(); Matrix4 endMatrix; if (scale > 1.0) { // Zoom out endMatrix = Matrix4.identity(); } else { // Zoom in endMatrix = Matrix4.identity() ..translate(-position.dx * 1.5, -position.dy * 1.5) ..scale(2.5); } _animation = Matrix4Tween( begin: _transformationController.value, end: endMatrix, ).animate(CurveTween(curve: Curves.easeInOut).animate(_animationController)); _animationController.forward(from: 0); } @override Widget build(BuildContext context) { return GestureDetector( onDoubleTapDown: (d) => _doubleTapDetails = d, onDoubleTap: _handleDoubleTap, child: InteractiveViewer( transformationController: _transformationController, minScale: 1.0, maxScale: 4.0, child: Image( image: widget.image, fit: BoxFit.contain, ), ), ); } }