2026-01-13 11:36:24 +05:30

141 lines
4.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:get/get.dart';
class ImageZoomWidget extends StatefulWidget {
final String imageUrl;
final String? heroTag;
const ImageZoomWidget({super.key, required this.imageUrl, this.heroTag});
@override
State<ImageZoomWidget> createState() => _ImageZoomWidgetState();
}
class _ImageZoomWidgetState extends State<ImageZoomWidget>
with SingleTickerProviderStateMixin {
late TransformationController _transformationController;
late AnimationController _animationController;
Animation<Matrix4>? _animation;
final RxBool _isZoomed = false.obs;
Offset? _doubleTapPosition;
@override
void initState() {
super.initState();
_transformationController = TransformationController();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
// 👇 Listen to transformation changes
_transformationController.addListener(() {
final double scale = _transformationController.value.getMaxScaleOnAxis();
_isZoomed.value = scale > 1.0;
});
}
@override
void dispose() {
_transformationController.dispose();
_animationController.dispose();
super.dispose();
}
void _onDoubleTap() {
final Matrix4 currentMatrix = _transformationController.value;
final double currentScale = currentMatrix.getMaxScaleOnAxis();
Matrix4 targetMatrix;
if (currentScale > 1.1) {
targetMatrix = Matrix4.identity();
} else {
final double scale = 2.5;
final Offset tapPos = _doubleTapPosition ?? Offset.zero;
targetMatrix =
Matrix4.identity()
..translate(tapPos.dx, tapPos.dy)
..scale(scale)
..translate(-tapPos.dx, -tapPos.dy);
}
_animation = Matrix4Tween(begin: currentMatrix, end: targetMatrix).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
_animationController
..reset()
..forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.black,
iconTheme: const IconThemeData(color: Colors.white),
elevation: 0,
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.arrow_back_ios),
),
),
body: SafeArea(
child: SizedBox.expand(
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
if (_animation != null) {
_transformationController.value = _animation!.value;
}
return GestureDetector(
onDoubleTapDown: (details) {
_doubleTapPosition = details.localPosition;
},
onDoubleTap: _onDoubleTap,
child: Obx(
() => InteractiveViewer(
transformationController: _transformationController,
minScale: 1.0,
maxScale: 10.0,
panEnabled: _isZoomed.value,
boundaryMargin: EdgeInsets.zero,
constrained: true,
clipBehavior: Clip.none,
child: Center(
child: Hero(
tag: widget.heroTag ?? 'image_${widget.imageUrl}',
child: CachedNetworkImage(
imageUrl: widget.imageUrl,
fit: BoxFit.contain,
placeholder:
(context, url) => const Center(
child: CircularProgressIndicator(
color: Colors.white,
),
),
errorWidget:
(context, url, error) => const Center(
child: Icon(
Icons.error,
color: Colors.white,
size: 50,
),
),
),
),
),
),
),
);
},
),
),
),
);
}
}