import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:mymuseum_visitapp/Models/visitContext.dart'; import 'package:mymuseum_visitapp/app_context.dart'; import 'package:provider/provider.dart'; class PuzzlePiece extends StatefulWidget { final CachedNetworkImage image; final Size imageSize; final int row; final int col; final int maxRow; final int maxCol; final Function bringToTop; final Function sendToBack; static PuzzlePiece fromMap(Map map) { return PuzzlePiece( image: map['image'], imageSize: map['imageSize'], row: map['row'], col: map['col'], maxRow: map['maxRow'], maxCol: map['maxCol'], bringToTop: map['bringToTop'], sendToBack: map['SendToBack'], ); } PuzzlePiece( {Key? key, required this.image, required this.imageSize, required this.row, required this.col, required this.maxRow, required this.maxCol, required this.bringToTop, required this.sendToBack}) : super(key: key); @override _PuzzlePieceState createState() => _PuzzlePieceState(); } class _PuzzlePieceState extends State { // the piece initial top offset double? top; // the piece initial left offset double? left; // can we move the piece ? bool isMovable = true; GlobalKey _widgetPieceKey = GlobalKey(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { RenderBox renderBox = _widgetPieceKey.currentContext?.findRenderObject() as RenderBox; Size size = renderBox.size; final appContext = Provider.of(context, listen: false); VisitAppContext visitAppContext = appContext.getContext(); visitAppContext.puzzleSize = size; // do it another way appContext.setContext(visitAppContext); if(widget.row == 0 && widget.col == 0) { widget.sendToBack(widget); } }); }); } @override Widget build(BuildContext context) { var isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; var imageHeight = isPortrait ? widget.imageSize.width/1.55 : widget.imageSize.width; var imageWidth = isPortrait ? widget.imageSize.width/1.55 : widget.imageSize.height; final pieceWidth = imageWidth / widget.maxCol; final pieceHeight = imageHeight / widget.maxRow; if (top == null) { top = Random().nextInt((imageHeight - pieceHeight).ceil()).toDouble(); var test = top!; test -= widget.row * pieceHeight; top = test /7; // TODO change ? } if (left == null) { left = Random().nextInt((imageWidth - pieceWidth).ceil()).toDouble(); var test = left!; test -= widget.col * pieceWidth; left = test /7; // TODO change ? } if(widget.row == 0 && widget.col == 0) { top = 0; left = 0; isMovable = false; } return Positioned( top: top, left: left, width: imageWidth, child: Container( key: _widgetPieceKey, decoration: widget.col == 0 && widget.row == 0 ? BoxDecoration( border: Border.all( color: Colors.black, width: 0.5, ), ) : null, child: GestureDetector( onTap: () { if (isMovable) { widget.bringToTop(widget); } }, onPanStart: (_) { if (isMovable) { widget.bringToTop(widget); } }, onPanUpdate: (dragUpdateDetails) { if (isMovable) { setState(() { var testTop = top!; var testLeft = left!; testTop = top!; testLeft = left!; testTop += dragUpdateDetails.delta.dy; testLeft += dragUpdateDetails.delta.dx; top = testTop; left = testLeft; if (-10 < top! && top! < 10 && -10 < left! && left! < 10) { top = 0; left = 0; isMovable = false; widget.sendToBack(widget); } }); } }, child: ClipPath( child: CustomPaint( foregroundPainter: PuzzlePiecePainter( widget.row, widget.col, widget.maxRow, widget.maxCol), child: widget.image), clipper: PuzzlePieceClipper( widget.row, widget.col, widget.maxRow, widget.maxCol), ), ), )); } } // this class is used to clip the image to the puzzle piece path class PuzzlePieceClipper extends CustomClipper { final int row; final int col; final int maxRow; final int maxCol; PuzzlePieceClipper(this.row, this.col, this.maxRow, this.maxCol); @override Path getClip(Size size) { return getPiecePath(size, row, col, maxRow, maxCol); } @override bool shouldReclip(CustomClipper oldClipper) => false; } // this class is used to draw a border around the clipped image class PuzzlePiecePainter extends CustomPainter { final int row; final int col; final int maxRow; final int maxCol; PuzzlePiecePainter(this.row, this.col, this.maxRow, this.maxCol); @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = Colors.black//Color(0x80FFFFFF) ..style = PaintingStyle.stroke ..strokeWidth = 2.5; canvas.drawPath(getPiecePath(size, row, col, maxRow, maxCol), paint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return false; } } // this is the path used to clip the image and, then, to draw a border around it; here we actually draw the puzzle piece Path getPiecePath(Size size, int row, int col, int maxRow, int maxCol) { final width = size.width / maxCol; final height = size.height / maxRow; final offsetX = col * width; final offsetY = row * height; final bumpSize = height / 4; var path = Path(); path.moveTo(offsetX, offsetY); if (row == 0) { // top side piece path.lineTo(offsetX + width, offsetY); } else { // top bump path.lineTo(offsetX + width / 3, offsetY); path.cubicTo( offsetX + width / 6, offsetY - bumpSize, offsetX + width / 6 * 5, offsetY - bumpSize, offsetX + width / 3 * 2, offsetY); path.lineTo(offsetX + width, offsetY); } if (col == maxCol - 1) { // right side piece path.lineTo(offsetX + width, offsetY + height); } else { // right bump path.lineTo(offsetX + width, offsetY + height / 3); path.cubicTo( offsetX + width - bumpSize, offsetY + height / 6, offsetX + width - bumpSize, offsetY + height / 6 * 5, offsetX + width, offsetY + height / 3 * 2); path.lineTo(offsetX + width, offsetY + height); } if (row == maxRow - 1) { // bottom side piece path.lineTo(offsetX, offsetY + height); } else { // bottom bump path.lineTo(offsetX + width / 3 * 2, offsetY + height); path.cubicTo( offsetX + width / 6 * 5, offsetY + height - bumpSize, offsetX + width / 6, offsetY + height - bumpSize, offsetX + width / 3, offsetY + height); path.lineTo(offsetX, offsetY + height); } if (col == 0) { // left side piece path.close(); } else { // left bump path.lineTo(offsetX, offsetY + height / 3 * 2); path.cubicTo( offsetX - bumpSize, offsetY + height / 6 * 5, offsetX - bumpSize, offsetY + height / 6, offsetX, offsetY + height / 3); path.close(); } return path; }