139 lines
3.5 KiB
Dart
139 lines
3.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
class AttachmentGalleryScreen extends StatefulWidget {
|
|
final List<ImageProvider> images;
|
|
final int initialIndex;
|
|
|
|
const AttachmentGalleryScreen({
|
|
super.key,
|
|
required this.images,
|
|
required this.initialIndex,
|
|
});
|
|
|
|
@override
|
|
State<AttachmentGalleryScreen> createState() => _AttachmentGalleryScreenState();
|
|
}
|
|
|
|
class _AttachmentGalleryScreenState extends State<AttachmentGalleryScreen> {
|
|
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<Matrix4>? _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,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|