fixed slot machine

This commit is contained in:
Deep Koluguri 2025-11-06 19:25:19 -05:00
parent 004328489d
commit a56c3e0922
3 changed files with 469 additions and 46 deletions

View File

@ -7,6 +7,7 @@ import '../../core/models/chit_group.dart';
import '../../shared/widgets/draw_animation_selector.dart';
import '../../shared/widgets/recording_overlay.dart';
import '../../core/services/screen_recording_service.dart';
import 'draw_animation_page.dart';
class CombinedDrawDialog extends StatefulWidget {
final ChitGroup group;
@ -130,13 +131,36 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
}
}
void _startDraw() {
void _startDraw() async {
if (!_formKey.currentState!.validate()) return;
setState(() {
_isDrawStarted = true;
_generateSeeds();
});
// Generate seeds before navigation
_generateSeeds();
final month = int.parse(_monthController.text);
final year = int.parse(_yearController.text);
// Close this dialog and navigate to full-screen draw animation
Navigator.of(context).pop();
final result = await Get.to(
() => DrawAnimationPage(
group: widget.group,
month: month,
year: year,
serverSeed: _serverSeed!,
nonce: _nonce!,
eligibleMembers: _eligibleMembers,
),
transition: Transition.fadeIn,
duration: const Duration(milliseconds: 500),
);
// If draw was successful, reload draws in parent
if (result == true) {
final chitGroupService = Get.find<ChitGroupService>();
await chitGroupService.loadGroupMonthlyDraws(widget.group.id);
}
}
void _generateSeeds() {

View File

@ -0,0 +1,349 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../core/services/chit_group_service.dart';
import '../../core/models/chit_group.dart';
import '../../shared/widgets/draw_animation_selector.dart';
class DrawAnimationPage extends StatefulWidget {
final ChitGroup group;
final int month;
final int year;
final String serverSeed;
final int nonce;
final List<Map<String, dynamic>> eligibleMembers;
const DrawAnimationPage({
super.key,
required this.group,
required this.month,
required this.year,
required this.serverSeed,
required this.nonce,
required this.eligibleMembers,
});
@override
State<DrawAnimationPage> createState() => _DrawAnimationPageState();
}
class _DrawAnimationPageState extends State<DrawAnimationPage>
with SingleTickerProviderStateMixin {
late AnimationController _fadeController;
late Animation<double> _fadeAnimation;
bool _isComplete = false;
@override
void initState() {
super.initState();
_fadeController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _fadeController,
curve: Curves.easeInOut,
));
_fadeController.forward();
}
@override
void dispose() {
_fadeController.dispose();
super.dispose();
}
void _onDrawComplete(String winnerId) async {
if (_isComplete) return;
setState(() {
_isComplete = true;
});
// Wait a moment to show the winner
await Future.delayed(const Duration(seconds: 3));
// Save the draw result
final chitGroupService = Get.find<ChitGroupService>();
try {
await chitGroupService.createMonthlyDraw(
widget.group.id,
widget.month,
widget.year,
clientSeed: 'DRAW_${DateTime.now().millisecondsSinceEpoch}',
);
// Navigate back with success
Get.back(result: true);
Get.snackbar(
'Draw Complete! 🎉',
'Winner has been selected successfully',
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
} catch (e) {
Get.back(result: false);
Get.snackbar(
'Error',
'Failed to save draw result: ${e.toString()}',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: const Duration(seconds: 4),
);
}
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Prevent back button during animation
if (_isComplete) return true;
final result = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Cancel Draw?'),
content: const Text('Are you sure you want to cancel the draw? This action cannot be undone.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Continue Draw'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Cancel Draw'),
),
],
),
);
return result ?? false;
},
child: Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: FadeTransition(
opacity: _fadeAnimation,
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.purple.shade900,
Colors.blue.shade900,
Colors.indigo.shade900,
],
),
),
child: Column(
children: [
// Header
Padding(
padding: EdgeInsets.all(20.w),
child: Column(
children: [
Row(
children: [
if (!_isComplete)
IconButton(
icon: Icon(
Icons.close,
color: Colors.white.withOpacity(0.8),
size: 28.w,
),
onPressed: () async {
final result = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Cancel Draw?'),
content: const Text('Are you sure you want to cancel the draw?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Continue'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Cancel'),
),
],
),
);
if (result == true) {
Get.back(result: false);
}
},
)
else
SizedBox(width: 48.w),
Expanded(
child: Column(
children: [
Text(
widget.group.name,
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
SizedBox(height: 8.h),
Container(
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 6.h,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20.r),
border: Border.all(
color: Colors.white.withOpacity(0.3),
),
),
child: Text(
'Month ${widget.month}/${widget.year}',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
],
),
),
SizedBox(width: 48.w),
],
),
],
),
),
// Animation Area
Expanded(
child: Center(
child: DrawAnimationSelector(
members: widget.eligibleMembers,
onDrawComplete: _onDrawComplete,
serverSeed: widget.serverSeed,
clientSeed: 'DRAW_${DateTime.now().millisecondsSinceEpoch}',
nonce: widget.nonce,
animationDuration: const Duration(seconds: 8),
),
),
),
// Footer
Padding(
padding: EdgeInsets.all(20.w),
child: Column(
children: [
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: Colors.white.withOpacity(0.2),
),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildInfoItem(
'Eligible Members',
'${widget.eligibleMembers.length}',
Icons.people,
),
Container(
width: 1,
height: 30.h,
color: Colors.white.withOpacity(0.3),
),
_buildInfoItem(
'Total Members',
'${widget.group.maxMembers}',
Icons.group,
),
],
),
],
),
),
SizedBox(height: 12.h),
Text(
'🎲 Provably Fair Draw',
style: TextStyle(
fontSize: 14.sp,
color: Colors.white.withOpacity(0.7),
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
),
),
),
),
);
}
Widget _buildInfoItem(String label, String value, IconData icon) {
return Column(
children: [
Icon(
icon,
color: Colors.white.withOpacity(0.8),
size: 24.w,
),
SizedBox(height: 6.h),
Text(
value,
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Text(
label,
style: TextStyle(
fontSize: 12.sp,
color: Colors.white.withOpacity(0.7),
),
),
],
);
}
}

View File

@ -403,7 +403,6 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
String? _winnerId;
List<String> _displayNames = [];
Timer? _slotTimer;
int _currentSelectedIndex = 0;
@override
void initState() {
@ -491,8 +490,6 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
setState(() {
// Shuffle the display names for effect
_displayNames.shuffle();
// Update current selection pointer
_currentSelectedIndex = currentSpin % _displayNames.length;
});
_slotController.forward().then((_) {
@ -516,7 +513,6 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
setState(() {
_displayNames.shuffle();
_currentSelectedIndex = currentSpin % _displayNames.length;
});
_slotController.forward().then((_) {
@ -531,15 +527,27 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
}
void _completeAnimation() {
final winnerName = widget.members.firstWhere((m) => m['id'] == _winnerId)['name'];
setState(() {
_isAnimating = false;
_isComplete = true;
// Set the winner's name at the top
_displayNames = [widget.members.firstWhere((m) => m['id'] == _winnerId)['name']];
// Show winner in center position (index 2) with padding names
_displayNames = [
_displayNames.isNotEmpty ? _displayNames[0] : '',
_displayNames.length > 1 ? _displayNames[1] : '',
winnerName, // Center - the winner!
_displayNames.length > 3 ? _displayNames[3] : '',
_displayNames.length > 4 ? _displayNames[4] : '',
];
});
_pulseController.stop();
widget.onDrawComplete(_winnerId!);
// Delay callback slightly for visual effect
Future.delayed(const Duration(milliseconds: 500), () {
widget.onDrawComplete(_winnerId!);
});
}
String _generateHash(String input) {
@ -640,55 +648,97 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
animation: _slotAnimation,
builder: (context, child) {
return Column(
children: _displayNames.take(5).map((name) {
final index = _displayNames.indexOf(name);
final isWinner = _isComplete && index == 0;
final isCurrentlySelected = _isAnimating && index == _currentSelectedIndex;
children: List.generate(5, (index) {
final displayIndex = index < _displayNames.length ? index : 0;
final name = _displayNames[displayIndex];
final isWinner = _isComplete && index == 2; // Center position
final isCenterHighlight = _isAnimating && index == 2; // Always highlight center
return Expanded(
child: AnimatedBuilder(
animation: _pulseAnimation,
builder: (context, child) {
return Transform.scale(
scale: isCurrentlySelected ? _pulseAnimation.value : 1.0,
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
transform: Matrix4.identity()
..scale(isCenterHighlight || isWinner ? 1.05 : 1.0),
child: Container(
width: double.infinity,
margin: EdgeInsets.symmetric(vertical: 2.h),
margin: EdgeInsets.symmetric(vertical: 2.h, horizontal: 4.w),
decoration: BoxDecoration(
color: isWinner
? Colors.green.shade600
: isCurrentlySelected
? Colors.red.shade600
: Colors.blue.shade600,
borderRadius: BorderRadius.circular(4.r),
boxShadow: isWinner ? [
BoxShadow(
color: Colors.green.shade300,
blurRadius: 8.r,
spreadRadius: 2.r,
),
] : isCurrentlySelected ? [
BoxShadow(
color: Colors.red.shade300,
blurRadius: 6.r,
spreadRadius: 1.r,
),
] : null,
gradient: isWinner
? LinearGradient(
colors: [
Colors.green.shade600,
Colors.green.shade700,
],
)
: isCenterHighlight
? LinearGradient(
colors: [
Colors.orange.shade600,
Colors.red.shade600,
],
)
: LinearGradient(
colors: [
Colors.blue.shade700,
Colors.blue.shade800,
],
),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(
color: isWinner || isCenterHighlight
? Colors.white.withOpacity(0.6)
: Colors.white.withOpacity(0.1),
width: isWinner || isCenterHighlight ? 2.w : 1.w,
),
boxShadow: isWinner
? [
BoxShadow(
color: Colors.green.shade300,
blurRadius: 12.r,
spreadRadius: 3.r,
),
]
: isCenterHighlight
? [
BoxShadow(
color: Colors.orange.shade300.withOpacity(0.6),
blurRadius: 8.r,
spreadRadius: 2.r,
),
]
: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 4.r,
offset: Offset(0, 2.h),
),
],
),
child: Center(
child: Text(
name.length > 12 ? '${name.substring(0, 12)}...' : name,
name.length > 15 ? '${name.substring(0, 15)}...' : name,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
fontSize: isWinner || isCenterHighlight ? 18.sp : 15.sp,
fontWeight: isWinner || isCenterHighlight
? FontWeight.w900
: FontWeight.w600,
color: Colors.white,
shadows: isWinner ? [
letterSpacing: 0.5,
shadows: [
Shadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 2.r,
color: Colors.black.withOpacity(0.5),
blurRadius: 3.r,
offset: Offset(1, 1),
),
] : null,
],
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
@ -696,7 +746,7 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
},
),
);
}).toList(),
}),
);
},
),