Created
July 28, 2025 22:55
-
-
Save valterh4ck3r/933084477a1de58402ea4d34518d6daf to your computer and use it in GitHub Desktop.
Rematch Flutter App
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
class CollapsibleActions extends StatefulWidget { | |
const CollapsibleActions({super.key , required this.refreshAction , required this.eraseAction}); | |
final Function refreshAction; | |
final Function eraseAction; | |
@override | |
State<CollapsibleActions> createState() => _CollapsibleActionsState(); | |
} | |
class _CollapsibleActionsState extends State<CollapsibleActions> { | |
bool isExpanded = false; | |
@override | |
Widget build(BuildContext context) { | |
return Positioned( | |
right: 24, | |
bottom: 24, | |
child: AnimatedContainer( | |
duration: Duration(milliseconds: 300), | |
curve: Curves.easeInOut, | |
width: isExpanded ? 160 : 68, | |
height: isExpanded ? 68 : 68, | |
decoration: BoxDecoration( | |
color: isExpanded ? Colors.white : Colors.transparent, | |
borderRadius: BorderRadius.circular(28), | |
), | |
child: Row( | |
children: [ | |
isExpanded ? buildExpanded() : buildCollapsed(), | |
], | |
), | |
), | |
); | |
} | |
Widget buildExpanded() { | |
return Expanded( | |
child: ListView( | |
scrollDirection: Axis.horizontal, | |
padding: EdgeInsets.symmetric(horizontal: 8), | |
children: [ | |
IconButton( | |
icon: Icon(Icons.close, color: Colors.black), | |
onPressed: () => setState(() { isExpanded = !isExpanded; }), | |
), | |
IconButton( | |
icon: Icon(Icons.refresh, color: Colors.black), | |
onPressed: () => widget.refreshAction.call(), | |
), | |
IconButton( | |
icon: Icon(Icons.delete_forever, color: Colors.black), | |
onPressed: () => widget.eraseAction.call(), | |
), | |
], | |
), | |
); | |
} | |
Widget buildCollapsed() { | |
return IconButton( | |
icon: Icon(Icons.settings, color: Colors.white, size: 48,), | |
onPressed: () => setState(() { isExpanded = !isExpanded; }), | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
import '../widgets/collapsible_actions.dart'; | |
class MainScreen extends StatefulWidget { | |
const MainScreen({super.key}); | |
@override | |
State<MainScreen> createState() => _MainScreenState(); | |
} | |
class _MainScreenState extends State<MainScreen> { | |
String backgroundPath = "assets/images/field-rematch.png"; | |
String allyMarkerPath = "assets/images/ally-marker.png"; | |
String enemyMarkerPath = "assets/images/enemy-marker.png"; | |
String ballMarkerPath = "assets/images/ball-marker.png"; | |
late Offset positionAlly1; | |
late Offset positionAlly2; | |
late Offset positionAlly3; | |
late Offset positionAlly4; | |
late Offset positionAlly5; | |
late Offset positionEnemy1; | |
late Offset positionEnemy2; | |
late Offset positionEnemy3; | |
late Offset positionEnemy4; | |
late Offset positionEnemy5; | |
late Offset positionBall; | |
Offset dragStartOffset = Offset.zero; | |
late Widget allyMarkerWidget; | |
late Widget enemyMarkerWidget; | |
late Widget ballMarkerWidget; | |
late List<Offset> points; | |
bool firstBuild = true; | |
@override | |
void initState() { | |
allyMarkerWidget = SizedBox( | |
width: 40, | |
height: 40, | |
child: Image.asset( | |
allyMarkerPath, | |
fit: BoxFit.cover, | |
), | |
); | |
enemyMarkerWidget = SizedBox( | |
width: 40, | |
height: 40, | |
child: Image.asset( | |
enemyMarkerPath, | |
fit: BoxFit.cover, | |
), | |
); | |
ballMarkerWidget = SizedBox( | |
width: 40, | |
height: 40, | |
child: Image.asset( | |
ballMarkerPath, | |
fit: BoxFit.cover, | |
), | |
); | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
if(firstBuild){ | |
startMarkers(context); | |
startPoints(context); | |
firstBuild = false; | |
} | |
return Scaffold( | |
backgroundColor: Colors.black, | |
body: buildContent(), | |
); | |
} | |
Widget buildContent() { | |
return SizedBox( | |
width: MediaQuery.of(context).size.width, | |
height: MediaQuery.of(context).size.height, | |
child: Stack( | |
children: [ | |
// Background | |
Positioned.fill( | |
child: Center( | |
child: Image.asset( | |
backgroundPath, | |
fit: BoxFit.fitHeight, | |
width: 600, | |
height: 900, | |
), | |
), | |
), | |
// Custom Paint | |
Positioned.fill( | |
child: GestureDetector( | |
onPanUpdate: (details) { | |
RenderBox box = context.findRenderObject() as RenderBox; | |
Offset localPosition = | |
box.globalToLocal(details.globalPosition); | |
setState(() { | |
points.add(localPosition); | |
}); | |
}, | |
onPanEnd: (details) { | |
points.add(Offset.zero); // Para separar os traços | |
}, | |
child: CustomPaint( | |
painter: ScratchPainter(points), | |
), | |
), | |
), | |
// Enemy Markers | |
...buildEnemyMarkers(), | |
// Ally Markers | |
...buildAllyMarkers(), | |
// Ball Marker | |
Positioned( | |
left: positionBall.dx, | |
top: positionBall.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionBall += details.delta; | |
print(positionBall); | |
}); | |
}, | |
child: ballMarkerWidget, | |
), | |
), | |
// Actions | |
CollapsibleActions( | |
refreshAction: () { | |
setState(() { | |
startMarkers(context); | |
}); | |
}, | |
eraseAction: () { | |
setState(() { | |
startPoints(context); | |
}); | |
}, | |
) | |
], | |
), | |
); | |
} | |
List<Widget> buildEnemyMarkers() { | |
return [ | |
// Enemy Marker 1 | |
Positioned( | |
left: positionEnemy1.dx, | |
top: positionEnemy1.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionEnemy1 += details.delta; | |
}); | |
}, | |
child: enemyMarkerWidget, | |
), | |
), | |
// Enemy Marker 2 | |
Positioned( | |
left: positionEnemy2.dx, | |
top: positionEnemy2.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionEnemy2 += details.delta; | |
}); | |
}, | |
child: enemyMarkerWidget, | |
), | |
), | |
// Enemy Marker 3 | |
Positioned( | |
left: positionEnemy3.dx, | |
top: positionEnemy3.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionEnemy3 += details.delta; | |
}); | |
}, | |
child: enemyMarkerWidget, | |
), | |
), | |
// Enemy Marker 4 | |
Positioned( | |
left: positionEnemy4.dx, | |
top: positionEnemy4.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionEnemy4 += details.delta; | |
}); | |
}, | |
child: enemyMarkerWidget, | |
), | |
), | |
// Enemy Marker 5 | |
Positioned( | |
left: positionEnemy5.dx, | |
top: positionEnemy5.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionEnemy5 += details.delta; | |
}); | |
}, | |
child: enemyMarkerWidget, | |
), | |
), | |
]; | |
} | |
List<Widget> buildAllyMarkers() { | |
return [ | |
Positioned( | |
left: positionAlly1.dx, | |
top: positionAlly1.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionAlly1 += details.delta; | |
}); | |
}, | |
child: allyMarkerWidget, | |
), | |
), | |
// Ally Enemy 2 | |
Positioned( | |
left: positionAlly2.dx, | |
top: positionAlly2.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionAlly2 += details.delta; | |
}); | |
}, | |
child: allyMarkerWidget, | |
), | |
), | |
// Ally Enemy 3 | |
Positioned( | |
left: positionAlly3.dx, | |
top: positionAlly3.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionAlly3 += details.delta; | |
}); | |
}, | |
child: allyMarkerWidget, | |
), | |
), | |
// Ally Enemy 4 | |
Positioned( | |
left: positionAlly4.dx, | |
top: positionAlly4.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionAlly4 += details.delta; | |
}); | |
}, | |
child: allyMarkerWidget, | |
), | |
), | |
// Ally Enemy 5 | |
Positioned( | |
left: positionAlly5.dx, | |
top: positionAlly5.dy, | |
child: GestureDetector( | |
onPanStart: (details) { | |
dragStartOffset = details.localPosition; | |
}, | |
onPanUpdate: (details) { | |
setState(() { | |
positionAlly5 += details.delta; | |
}); | |
}, | |
child: allyMarkerWidget, | |
), | |
), | |
]; | |
} | |
startMarkers(BuildContext context) { | |
positionAlly1 = Offset(MediaQuery.of(context).size.width * 0.495, MediaQuery.of(context).size.height * 0.92); | |
positionAlly2 = Offset(MediaQuery.of(context).size.width * 0.495, MediaQuery.of(context).size.height * 0.51); | |
positionAlly3 = Offset(MediaQuery.of(context).size.width * 0.40, MediaQuery.of(context).size.height * 0.60); | |
positionAlly4 = Offset(MediaQuery.of(context).size.width * 0.495, MediaQuery.of(context).size.height * 0.65); | |
positionAlly5 = Offset(MediaQuery.of(context).size.width * 0.60, MediaQuery.of(context).size.height * 0.60); | |
positionEnemy1 = Offset(MediaQuery.of(context).size.width * 0.495, MediaQuery.of(context).size.width * 0.01); | |
positionEnemy2 = Offset(MediaQuery.of(context).size.width * 0.40, MediaQuery.of(context).size.height * 0.25); | |
positionEnemy3 = Offset(MediaQuery.of(context).size.width * 0.40, MediaQuery.of(context).size.height * 0.45); | |
positionEnemy4 = Offset(MediaQuery.of(context).size.width * 0.60, MediaQuery.of(context).size.height * 0.25); | |
positionEnemy5 = Offset(MediaQuery.of(context).size.width * 0.60, MediaQuery.of(context).size.height * 0.45); | |
positionBall = Offset(MediaQuery.of(context).size.width * 0.49, MediaQuery.of(context).size.height * 0.47); | |
} | |
startPoints(BuildContext context) { | |
points = []; | |
} | |
} | |
class ScratchPainter extends CustomPainter { | |
final List<Offset> points; | |
ScratchPainter(this.points); | |
@override | |
void paint(Canvas canvas, Size size) { | |
final paint = Paint() | |
..color = Colors.white | |
..strokeWidth = 5.0 | |
..strokeCap = StrokeCap.round; | |
for (int i = 0; i < points.length - 1; i++) { | |
if (points[i] != Offset.zero && points[i + 1] != Offset.zero) { | |
canvas.drawLine(points[i], points[i + 1], paint); | |
} | |
} | |
} | |
@override | |
bool shouldRepaint(ScratchPainter oldDelegate) => true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment