fixed animations

This commit is contained in:
Deep Koluguri 2026-04-05 23:43:12 -04:00
parent 5c284a3698
commit 1c861e1f4b
5 changed files with 856 additions and 522 deletions

View File

@ -0,0 +1,187 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'app_theme.dart';
/// Visual tokens for the full-screen monthly draw / slot machine.
/// Anchored to [AppTheme] teal seed + amber tertiary; respects light/dark [ColorScheme].
class DrawSlotTheming {
DrawSlotTheming(this.scheme, this.brightness);
final ColorScheme scheme;
final Brightness brightness;
bool get _isDark => brightness == Brightness.dark;
/// Immersive page background (draw route).
LinearGradient get pageBackdrop {
if (_isDark) {
final deep = Color.alphaBlend(
scheme.primary.withOpacity(0.5),
const Color(0xFF041014),
);
final mid = Color.alphaBlend(
scheme.primary.withOpacity(0.22),
const Color(0xFF0A1C22),
);
return LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
stops: const [0.0, 0.55, 1.0],
colors: [
deep,
mid,
Color.alphaBlend(scheme.surface.withOpacity(0.15), const Color(0xFF020608)),
],
);
}
return LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color.alphaBlend(scheme.primary.withOpacity(0.12), scheme.surface),
scheme.surface,
Color.alphaBlend(scheme.primaryContainer.withOpacity(0.35), scheme.surface),
],
);
}
Color get pageOnText =>
_isDark ? Colors.white.withOpacity(0.95) : scheme.onSurface;
Color get pageMuted =>
_isDark ? Colors.white.withOpacity(0.72) : scheme.onSurfaceVariant;
Color get chipFill => _isDark
? Colors.white.withOpacity(0.12)
: scheme.primaryContainer.withOpacity(0.45);
Color get chipBorder =>
_isDark ? Colors.white.withOpacity(0.22) : scheme.outlineVariant;
/// Pre-start intro card (on draw page).
BoxDecoration introCardDecoration() {
return BoxDecoration(
color: _isDark
? Color.alphaBlend(Colors.white.withOpacity(0.08), const Color(0xFF0C181C))
: scheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20.r),
border: Border.all(
color: _isDark
? Colors.white.withOpacity(0.14)
: scheme.outlineVariant,
width: 1,
),
boxShadow: [
BoxShadow(
color: scheme.primary.withOpacity(_isDark ? 0.18 : 0.08),
blurRadius: 24.r,
offset: Offset(0, 12.h),
),
],
);
}
/// Slot machine outer chassis.
BoxDecoration slotChassisDecoration() {
return BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: _isDark
? [
Color.alphaBlend(scheme.surfaceContainerHigh.withOpacity(0.4), const Color(0xFF0D1619)),
const Color(0xFF050A0C),
]
: [
scheme.surfaceContainerHigh,
scheme.surfaceContainer,
],
),
borderRadius: BorderRadius.circular(22.r),
border: Border.all(
color: Color.alphaBlend(
scheme.tertiary.withOpacity(0.85),
scheme.outline,
),
width: 2.w,
),
boxShadow: [
BoxShadow(
color: scheme.primary.withOpacity(_isDark ? 0.35 : 0.12),
blurRadius: 28.r,
spreadRadius: -2,
offset: Offset(0, 14.h),
),
BoxShadow(
color: Colors.black.withOpacity(_isDark ? 0.45 : 0.06),
blurRadius: 20.r,
offset: Offset(0, 8.h),
),
],
);
}
Color get slotInnerWell => _isDark
? const Color(0xFF020608)
: Color.alphaBlend(scheme.surfaceContainerHighest, scheme.surface);
List<Color> rowColorsIdle() => _isDark
? [
Color.alphaBlend(scheme.surfaceContainerHigh.withOpacity(0.5), const Color(0xFF1A2529)),
Color.alphaBlend(scheme.primary.withOpacity(0.35), const Color(0xFF0F181C)),
]
: [
scheme.surfaceContainerHigh,
scheme.surfaceContainer,
];
List<Color> rowColorsSpinning() => [
Color.alphaBlend(scheme.primary.withOpacity(0.55), rowColorsIdle().first),
Color.alphaBlend(scheme.primary.withOpacity(0.4), rowColorsIdle().last),
];
List<Color> rowColorsWinner() => _isDark
? [
Color.alphaBlend(scheme.primary.withOpacity(0.9), const Color(0xFF0A3030)),
Color.alphaBlend(scheme.tertiary.withOpacity(0.35), scheme.primary),
]
: [
scheme.primary,
Color.alphaBlend(scheme.tertiary.withOpacity(0.28), scheme.primary),
];
Color get winnerRowText =>
_isDark ? Colors.white : scheme.onPrimary;
Color get idleRowText => _isDark
? Colors.white.withOpacity(0.92)
: scheme.onSurface;
Color get winnerFrameColor =>
Color.alphaBlend(scheme.tertiary.withOpacity(0.95), AppTheme.accent);
BoxDecoration winnerSummaryDecoration() {
return BoxDecoration(
gradient: LinearGradient(
colors: [
Color.alphaBlend(scheme.primaryContainer.withOpacity(_isDark ? 0.35 : 0.65), scheme.surface),
Color.alphaBlend(scheme.tertiary.withOpacity(_isDark ? 0.12 : 0.2), scheme.surface),
],
),
borderRadius: BorderRadius.circular(18.r),
border: Border.all(
color: scheme.primary.withOpacity(0.35),
width: 1.5,
),
);
}
TextStyle winnerSummaryTitle(TextTheme t) => (t.titleLarge ?? const TextStyle()).copyWith(
fontWeight: FontWeight.w800,
color: scheme.primary,
height: 1.2,
);
Color get winnerSummaryIcon => scheme.tertiary;
}

View File

@ -258,6 +258,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return RecordingOverlay( return RecordingOverlay(
showRecordingIndicator: true, showRecordingIndicator: true,
child: Dialog( child: Dialog(
@ -272,7 +273,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
Container( Container(
padding: EdgeInsets.all(16.w), padding: EdgeInsets.all(16.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.purple.shade600, color: scheme.primary,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.r), topLeft: Radius.circular(16.r),
topRight: Radius.circular(16.r), topRight: Radius.circular(16.r),
@ -281,8 +282,8 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
child: Row( child: Row(
children: [ children: [
Icon( Icon(
Icons.casino, Icons.emoji_events_rounded,
color: Colors.white, color: scheme.onPrimary,
size: 24.w, size: 24.w,
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
@ -293,7 +294,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
Text( Text(
'Monthly Draw', 'Monthly Draw',
style: TextStyle( style: TextStyle(
color: Colors.white, color: scheme.onPrimary,
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -301,7 +302,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
Text( Text(
'${_monthController.text}/${_yearController.text}', '${_monthController.text}/${_yearController.text}',
style: TextStyle( style: TextStyle(
color: Colors.white.withOpacity(0.9), color: scheme.onPrimary.withOpacity(0.9),
fontSize: 12.sp, fontSize: 12.sp,
), ),
), ),
@ -310,7 +311,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
), ),
IconButton( IconButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close, color: Colors.white), icon: Icon(Icons.close, color: scheme.onPrimary),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
), ),
@ -342,9 +343,12 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
Container( Container(
padding: EdgeInsets.all(16.w), padding: EdgeInsets.all(16.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Theme.of(context).colorScheme.surface,
border: Border( border: Border(
top: BorderSide(color: Colors.grey.shade200, width: 1), top: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant,
width: 1,
),
), ),
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(16.r), bottomLeft: Radius.circular(16.r),
@ -374,11 +378,11 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
child: ElevatedButton( child: ElevatedButton(
onPressed: _isLoading ? null : _startDraw, onPressed: _isLoading ? null : _startDraw,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple.shade600, backgroundColor: scheme.primary,
foregroundColor: Colors.white, foregroundColor: scheme.onPrimary,
padding: EdgeInsets.symmetric(vertical: 14.h), padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r), borderRadius: BorderRadius.circular(12.r),
), ),
), ),
child: Row( child: Row(
@ -406,14 +410,14 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
), ),
if (_isDrawComplete) if (_isDrawComplete)
Expanded( Expanded(
child: ElevatedButton( child: FilledButton(
onPressed: _saveDrawResult, onPressed: _saveDrawResult,
style: ElevatedButton.styleFrom( style: FilledButton.styleFrom(
backgroundColor: Colors.green.shade600, backgroundColor: scheme.primary,
foregroundColor: Colors.white, foregroundColor: scheme.onPrimary,
padding: EdgeInsets.symmetric(vertical: 14.h), padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r), borderRadius: BorderRadius.circular(12.r),
), ),
), ),
child: Row( child: Row(
@ -440,6 +444,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
} }
Widget _buildDrawForm() { Widget _buildDrawForm() {
final scheme = Theme.of(context).colorScheme;
return Form( return Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
@ -451,7 +456,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.grey.shade800, color: scheme.onSurface,
), ),
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -555,14 +560,16 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
height: 16.w, height: 16.w,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.purple.shade600), valueColor: AlwaysStoppedAnimation<Color>(scheme.primary),
), ),
) )
else else
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h), padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _eligibleMembers.isEmpty ? Colors.orange.shade100 : Colors.purple.shade100, color: _eligibleMembers.isEmpty
? Colors.orange.shade100
: scheme.primaryContainer,
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Text( child: Text(
@ -570,7 +577,9 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: _eligibleMembers.isEmpty ? Colors.orange.shade800 : Colors.purple.shade800, color: _eligibleMembers.isEmpty
? Colors.orange.shade800
: scheme.onPrimaryContainer,
), ),
), ),
), ),
@ -637,12 +646,12 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
return ListTile( return ListTile(
dense: true, dense: true,
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: Colors.purple.shade100, backgroundColor: scheme.primaryContainer,
radius: 18.r, radius: 18.r,
child: Text( child: Text(
(member['name']?.isNotEmpty == true ? member['name'].substring(0, 1) : 'M').toUpperCase(), (member['name']?.isNotEmpty == true ? member['name'].substring(0, 1) : 'M').toUpperCase(),
style: TextStyle( style: TextStyle(
color: Colors.purple.shade700, color: scheme.primary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 14.sp, fontSize: 14.sp,
), ),
@ -660,8 +669,8 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
style: TextStyle(fontSize: 12.sp), style: TextStyle(fontSize: 12.sp),
), ),
trailing: Icon( trailing: Icon(
Icons.check_circle, Icons.check_circle_rounded,
color: Colors.green.shade600, color: scheme.primary,
size: 18.w, size: 18.w,
), ),
); );
@ -679,21 +688,22 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
} }
Widget _buildDrawResult() { Widget _buildDrawResult() {
final scheme = Theme.of(context).colorScheme;
return Column( return Column(
children: [ children: [
// Winner Announcement // Winner Announcement
Container( Container(
padding: EdgeInsets.all(20.w), padding: EdgeInsets.all(20.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.green.shade50, color: scheme.primaryContainer.withOpacity(0.65),
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(16.r),
border: Border.all(color: Colors.green.shade200), border: Border.all(color: scheme.primary.withOpacity(0.25)),
), ),
child: Column( child: Column(
children: [ children: [
Icon( Icon(
Icons.emoji_events, Icons.emoji_events_rounded,
color: Colors.amber.shade600, color: scheme.tertiary,
size: 48.w, size: 48.w,
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -702,7 +712,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
style: TextStyle( style: TextStyle(
fontSize: 20.sp, fontSize: 20.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.green.shade800, color: scheme.primary,
), ),
), ),
SizedBox(height: 8.h), SizedBox(height: 8.h),
@ -711,7 +721,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
style: TextStyle( style: TextStyle(
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.green.shade700, color: scheme.onSurface,
), ),
), ),
SizedBox(height: 4.h), SizedBox(height: 4.h),
@ -719,7 +729,7 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
_winnerData?['mobile'] ?? '', _winnerData?['mobile'] ?? '',
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.grey.shade600, color: scheme.onSurfaceVariant,
), ),
), ),
], ],
@ -731,20 +741,21 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
Container( Container(
padding: EdgeInsets.all(12.w), padding: EdgeInsets.all(12.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue.shade50, color: scheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8.r), borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: Colors.blue.shade200), border: Border.all(color: scheme.outlineVariant),
), ),
child: Row( child: Row(
children: [ children: [
Icon(Icons.security, color: Colors.blue.shade600, size: 16.w), Icon(Icons.verified_user_rounded, color: scheme.primary, size: 18.w),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Expanded( Expanded(
child: Text( child: Text(
'Draw completed using provably fair system. Recording saved.', 'Draw completed using provably fair system. Recording saved.',
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: Colors.blue.shade700, color: scheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
), ),
), ),
), ),
@ -771,11 +782,12 @@ class _CombinedDrawDialogState extends State<CombinedDrawDialog>
if (created != null) { if (created != null) {
Navigator.of(context).pop(); Navigator.of(context).pop();
final s = Theme.of(context).colorScheme;
Get.snackbar( Get.snackbar(
'Success', 'Success',
'Monthly draw completed successfully!', 'Monthly draw completed successfully!',
backgroundColor: Colors.green.shade600, backgroundColor: s.primary,
colorText: Colors.white, colorText: s.onPrimary,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
); );
} else { } else {

View File

@ -4,6 +4,7 @@ import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../core/services/chit_group_service.dart'; import '../../core/services/chit_group_service.dart';
import '../../core/models/chit_group.dart'; import '../../core/models/chit_group.dart';
import '../../core/themes/draw_slot_theme.dart';
import '../../shared/widgets/draw_animation_selector.dart'; import '../../shared/widgets/draw_animation_selector.dart';
import '../../core/utils/whatsapp_util.dart'; import '../../core/utils/whatsapp_util.dart';
@ -34,9 +35,81 @@ class _DrawAnimationPageState extends State<DrawAnimationPage>
late AnimationController _fadeController; late AnimationController _fadeController;
late Animation<double> _fadeAnimation; late Animation<double> _fadeAnimation;
bool _isComplete = false; bool _isComplete = false;
/// True after the draw is successfully persisted (safe to leave without prompt).
bool _drawRecorded = false;
/// Single client seed for the whole page so animation + API save use the same value. /// Single client seed for the whole page so animation + API save use the same value.
late final String _animationClientSeed; late final String _animationClientSeed;
void _exitDrawScreen({required bool recorded}) {
if (!mounted) return;
Navigator.of(context).pop(recorded);
}
/// System back / header always offers a way out.
Future<void> _handleLeaveIntent() async {
if (!_isComplete) {
final cancel = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Cancel draw?'),
content: const Text(
'The draw is still in progress. Stop and go back?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Keep drawing'),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
style: FilledButton.styleFrom(
backgroundColor: Colors.red.shade700,
foregroundColor: Colors.white,
),
child: const Text('Stop'),
),
],
),
);
if (cancel == true && mounted) {
_exitDrawScreen(recorded: false);
}
return;
}
if (_drawRecorded) {
_exitDrawScreen(recorded: true);
return;
}
final leave = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Leave without saving?'),
content: const Text(
'This winner is not recorded yet. If you leave now, you will need to run the draw again.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Stay'),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
style: FilledButton.styleFrom(
backgroundColor: Colors.red.shade700,
foregroundColor: Colors.white,
),
child: const Text('Leave anyway'),
),
],
),
);
if (leave == true && mounted) {
_exitDrawScreen(recorded: false);
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -104,8 +177,7 @@ class _DrawAnimationPageState extends State<DrawAnimationPage>
if (shouldSave == true) { if (shouldSave == true) {
await _saveDrawResult(winnerId, bidAmount); await _saveDrawResult(winnerId, bidAmount);
} else { } else {
// User cancelled, go back without saving _exitDrawScreen(recorded: false);
Get.back(result: false);
} }
} }
@ -162,6 +234,11 @@ class _DrawAnimationPageState extends State<DrawAnimationPage>
), ),
), ),
), ),
IconButton(
tooltip: 'Close',
onPressed: () => Navigator.pop(dialogContext, false),
icon: Icon(Icons.close, color: Colors.grey.shade700),
),
], ],
), ),
SizedBox(height: 8.h), SizedBox(height: 8.h),
@ -429,7 +506,6 @@ _Congratulations to the winner!_
Get.back(); Get.back();
if (created == null) { if (created == null) {
Get.back(result: false);
Get.snackbar( Get.snackbar(
'Error', 'Error',
'Failed to save draw result', 'Failed to save draw result',
@ -441,6 +517,10 @@ _Congratulations to the winner!_
return; return;
} }
setState(() {
_drawRecorded = true;
});
final drawId = created['id']?.toString(); final drawId = created['id']?.toString();
if (drawId != null) { if (drawId != null) {
final publicUrl = await WhatsAppUtil.getDrawPublicShareUrl(drawId); final publicUrl = await WhatsAppUtil.getDrawPublicShareUrl(drawId);
@ -459,7 +539,7 @@ _Congratulations to the winner!_
} }
if (!mounted) return; if (!mounted) return;
Get.back(result: true); _exitDrawScreen(recorded: true);
Get.snackbar( Get.snackbar(
'Draw Saved! 🎉', 'Draw Saved! 🎉',
@ -470,10 +550,13 @@ _Congratulations to the winner!_
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
); );
} catch (e) { } catch (e) {
if (mounted) Get.back(); if (mounted) {
try {
Get.back(result: false); Get.back();
} catch (_) {}
}
if (mounted) {
Get.snackbar( Get.snackbar(
'Error', 'Error',
'Failed to save draw result: ${e.toString()}', 'Failed to save draw result: ${e.toString()}',
@ -483,34 +566,47 @@ _Congratulations to the winner!_
); );
} }
} }
}
Future<void> _showPublicResultLinkDialog(String url) async { Future<void> _showPublicResultLinkDialog(String url) async {
await showDialog<void>( await showDialog<void>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: true,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: const Text('Public result link'), title: Row(
content: SelectableText( children: [
Expanded(child: const Text('Public result link')),
IconButton(
tooltip: 'Close',
onPressed: () => Navigator.of(ctx).pop(),
icon: const Icon(Icons.close),
),
],
),
content: SingleChildScrollView(
child: SelectableText(
url, url,
style: TextStyle(fontSize: 13.sp), style: TextStyle(fontSize: 13.sp),
), ),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('Close'),
),
FilledButton(
onPressed: () async { onPressed: () async {
await Clipboard.setData(ClipboardData(text: url)); await Clipboard.setData(ClipboardData(text: url));
if (ctx.mounted) Navigator.of(ctx).pop(); if (ctx.mounted) {
Get.snackbar( Get.snackbar(
'Copied', 'Copied',
'Link copied — anyone can open it in a browser', 'Link copied — anyone can open it in a browser',
snackPosition: SnackPosition.BOTTOM, snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 3), duration: const Duration(seconds: 3),
); );
}
}, },
child: const Text('Copy link'), child: const Text('Copy only'),
),
FilledButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('Done'),
), ),
], ],
), ),
@ -558,40 +654,17 @@ _Congratulations to the winner!_
]; ];
final drawPeriodShort = final drawPeriodShort =
'${monthLabels[widget.month - 1]} ${widget.year}'; '${monthLabels[widget.month - 1]} ${widget.year}';
final theme = Theme.of(context);
final d = DrawSlotTheming(theme.colorScheme, theme.brightness);
return PopScope( return PopScope(
canPop: _isComplete, canPop: false,
onPopInvokedWithResult: (didPop, result) async { onPopInvokedWithResult: (didPop, result) async {
if (didPop || !mounted) return; if (didPop || !mounted) return;
final leave = await showDialog<bool>( await _handleLeaveIntent();
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Leave draw?'),
content: const Text(
'The result is not saved yet. Leave anyway?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Stay'),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
style: FilledButton.styleFrom(
backgroundColor: Colors.red.shade700,
foregroundColor: Colors.white,
),
child: const Text('Leave'),
),
],
),
);
if (leave == true && mounted) {
Get.back(result: false);
}
}, },
child: Scaffold( child: Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.transparent,
body: SafeArea( body: SafeArea(
child: FadeTransition( child: FadeTransition(
opacity: _fadeAnimation, opacity: _fadeAnimation,
@ -599,93 +672,63 @@ _Congratulations to the winner!_
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: d.pageBackdrop,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.purple.shade900,
Colors.blue.shade900,
Colors.indigo.shade900,
],
),
), ),
child: Column( child: Column(
children: [ children: [
// Header // Header
Padding( Padding(
padding: EdgeInsets.all(20.w), padding: EdgeInsets.fromLTRB(8.w, 12.h, 12.w, 8.h),
child: Column( child: Column(
children: [ children: [
Row( Row(
children: [ children: [
if (!_isComplete)
IconButton( IconButton(
tooltip: _drawRecorded
? 'Done'
: (_isComplete
? 'Close'
: 'Cancel draw'),
icon: Icon( icon: Icon(
Icons.close, _drawRecorded
color: Colors.white.withOpacity(0.8), ? Icons.check_circle_outline_rounded
: Icons.close_rounded,
color: d.pageOnText,
size: 28.w, size: 28.w,
), ),
onPressed: () async { onPressed: () => _handleLeaveIntent(),
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( Expanded(
child: Column( child: Column(
children: [ children: [
Text( Text(
widget.group.name, widget.group.name,
style: TextStyle( style: theme.textTheme.headlineSmall?.copyWith(
fontSize: 24.sp, color: d.pageOnText,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w800,
color: Colors.white, letterSpacing: -0.3,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
SizedBox(height: 8.h), SizedBox(height: 10.h),
Container( Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 16.w, horizontal: 14.w,
vertical: 6.h, vertical: 8.h,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2), color: d.chipFill,
borderRadius: BorderRadius.circular(20.r), borderRadius: BorderRadius.circular(20.r),
border: Border.all( border: Border.all(color: d.chipBorder),
color: Colors.white.withOpacity(0.3),
),
), ),
child: Text( child: Text(
drawPeriodShort, drawPeriodShort,
style: TextStyle( style: theme.textTheme.labelLarge?.copyWith(
fontSize: 15.sp, color: d.pageOnText,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white, letterSpacing: 0.2,
), ),
), ),
), ),
@ -717,54 +760,63 @@ _Congratulations to the winner!_
// Footer // Footer
Padding( Padding(
padding: EdgeInsets.all(20.w), padding: EdgeInsets.fromLTRB(20.w, 8.h, 20.w, 16.h),
child: Column( child: Column(
children: [ children: [
Container( Container(
padding: EdgeInsets.all(16.w), padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 14.h,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1), color: d.chipFill,
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(18.r),
border: Border.all( border: Border.all(color: d.chipBorder),
color: Colors.white.withOpacity(0.2),
), ),
), child: Row(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
_buildInfoItem( _buildInfoItem(
'Eligible Members', d,
'In this draw',
'${widget.eligibleMembers.length}', '${widget.eligibleMembers.length}',
Icons.people, Icons.how_to_reg_rounded,
), ),
Container( Container(
width: 1, width: 1,
height: 30.h, height: 36.h,
color: Colors.white.withOpacity(0.3), color: d.chipBorder.withOpacity(0.6),
), ),
_buildInfoItem( _buildInfoItem(
'Total Members', d,
'Group size',
'${widget.group.maxMembers}', '${widget.group.maxMembers}',
Icons.group, Icons.groups_rounded,
), ),
], ],
), ),
],
), ),
SizedBox(height: 14.h),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.verified_user_rounded,
size: 16.w,
color: theme.colorScheme.tertiary,
), ),
SizedBox(height: 12.h), SizedBox(width: 8.w),
Text( Text(
'🎲 Provably Fair Draw', 'Provably fair · transparent draw',
style: TextStyle( style: theme.textTheme.bodyMedium?.copyWith(
fontSize: 14.sp, color: d.pageMuted,
color: Colors.white.withOpacity(0.7), fontWeight: FontWeight.w600,
fontWeight: FontWeight.w500,
), ),
), ),
], ],
), ),
],
),
), ),
], ],
), ),
@ -775,28 +827,35 @@ _Congratulations to the winner!_
); );
} }
Widget _buildInfoItem(String label, String value, IconData icon) { Widget _buildInfoItem(
DrawSlotTheming d,
String label,
String value,
IconData icon,
) {
return Column( return Column(
children: [ children: [
Icon( Icon(
icon, icon,
color: Colors.white.withOpacity(0.8), color: d.pageMuted,
size: 24.w, size: 22.w,
), ),
SizedBox(height: 6.h), SizedBox(height: 6.h),
Text( Text(
value, value,
style: TextStyle( style: TextStyle(
fontSize: 20.sp, fontSize: 20.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w800,
color: Colors.white, color: d.pageOnText,
), ),
), ),
Text( Text(
label, label,
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 11.sp,
color: Colors.white.withOpacity(0.7), fontWeight: FontWeight.w600,
color: d.pageMuted,
letterSpacing: 0.2,
), ),
), ),
], ],

View File

@ -4,6 +4,8 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:async'; import 'dart:async';
import '../../core/themes/draw_slot_theme.dart';
// Base class for all draw animations // Base class for all draw animations
abstract class DrawAnimation extends StatefulWidget { abstract class DrawAnimation extends StatefulWidget {
final List<Map<String, dynamic>> members; final List<Map<String, dynamic>> members;
@ -632,6 +634,10 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final d = DrawSlotTheming(scheme, theme.brightness);
String? winnerName; String? winnerName;
if (_winnerId != null && _winnerId!.isNotEmpty) { if (_winnerId != null && _winnerId!.isNotEmpty) {
for (final m in widget.members) { for (final m in widget.members) {
@ -644,57 +650,79 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
final maxW = MediaQuery.sizeOf(context).width - 40.w; final maxW = MediaQuery.sizeOf(context).width - 40.w;
return Center( return Semantics(
label: 'Slot machine draw',
child: Center(
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxW.clamp(280.w, 400.w)), constraints: BoxConstraints(maxWidth: maxW.clamp(280.w, 400.w)),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
height: 480.h.clamp(380.h, 560.h), height: 480.h.clamp(380.h, 560.h),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
if (_isAnimating)
Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 15.w,
height: 15.w,
child: CircularProgressIndicator(
strokeWidth: 2.2,
color: scheme.tertiary,
),
),
SizedBox(width: 10.w),
Text(
'Selecting winner…',
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w700,
color: scheme.onSurface,
),
),
],
),
),
Expanded( Expanded(
child: Container( child: Container(
width: double.infinity, width: double.infinity,
decoration: d.slotChassisDecoration(),
child: Padding(
padding: EdgeInsets.all(14.w),
child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( color: d.slotInnerWell,
colors: [ borderRadius: BorderRadius.circular(14.r),
Colors.grey.shade900, border: Border.all(
Colors.grey.shade800, color: scheme.outline.withOpacity(0.28),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
), ),
borderRadius: BorderRadius.circular(20.r),
border: Border.all(color: Colors.orange.shade400, width: 3.w),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.35),
blurRadius: 18.r,
offset: Offset(0, 10.h),
),
],
),
child: Container(
margin: EdgeInsets.all(18.w),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(12.r),
), ),
child: ClipRRect(
borderRadius: BorderRadius.circular(13.r),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final rowH = constraints.maxHeight / 7; final rowH = constraints.maxHeight / 7;
return Stack( return Stack(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
fit: StackFit.expand,
children: [ children: [
AnimatedBuilder( AnimatedBuilder(
animation: _slotAnimation, animation: _slotAnimation,
builder: (context, child) { builder: (context, child) {
return Column( return Column(
children: List.generate(7, (index) { children:
final displayIndex = List.generate(7, (index) {
index < _displayNames.length ? index : 0; final displayIndex = index <
final name = _displayNames[displayIndex]; _displayNames.length
final isWinner = _isComplete && index == 3; ? index
: 0;
final name =
_displayNames[displayIndex];
final isWinner =
_isComplete && index == 3;
final isCenterHighlight = final isCenterHighlight =
_isAnimating && index == 3; _isAnimating && index == 3;
@ -702,34 +730,28 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
child: AnimatedBuilder( child: AnimatedBuilder(
animation: _pulseAnimation, animation: _pulseAnimation,
builder: (context, child) { builder: (context, child) {
final double scale = final scale = isWinner ||
isWinner || isCenterHighlight isCenterHighlight
? 1.08 ? 1.05
: 1.0; : 1.0;
final double fontSize = isWinner List<Color> rowColors;
? 24.sp if (isWinner) {
rowColors = d.rowColorsWinner();
} else if (isCenterHighlight) {
rowColors =
d.rowColorsSpinning();
} else {
rowColors = d.rowColorsIdle();
}
final borderColor = isWinner
? scheme.tertiary
.withOpacity(0.9)
: isCenterHighlight : isCenterHighlight
? 20.sp ? scheme.primary
: 18.sp; .withOpacity(0.55)
final FontWeight weight = isWinner : scheme.outline
? FontWeight.w900 .withOpacity(0.22);
: isCenterHighlight
? FontWeight.w800
: FontWeight.w700;
final List<Color> colors = isWinner
? [
Colors.green.shade500,
Colors.green.shade600,
]
: isCenterHighlight
? [
Colors.deepPurple.shade500,
Colors.deepPurple.shade700,
]
: [
Colors.blueGrey.shade700,
Colors.blueGrey.shade900,
];
return AnimatedContainer( return AnimatedContainer(
duration: const Duration( duration: const Duration(
@ -739,37 +761,39 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
..scale(scale), ..scale(scale),
child: Container( child: Container(
width: double.infinity, width: double.infinity,
margin: EdgeInsets.symmetric( margin:
vertical: 6.h, EdgeInsets.symmetric(
horizontal: 12.w, vertical: 4.h,
horizontal: 10.w,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient:
colors: colors, LinearGradient(
begin: Alignment.topLeft, colors: rowColors,
end: Alignment.bottomRight, begin: Alignment
.centerLeft,
end: Alignment
.centerRight,
), ),
borderRadius: borderRadius:
BorderRadius.circular(10.r), BorderRadius
.circular(12.r),
border: Border.all( border: Border.all(
color: Colors.white.withOpacity( color: borderColor,
isWinner || width: isWinner
isCenterHighlight
? 0.7
: 0.15),
width: isWinner ||
isCenterHighlight
? 2.w ? 2.w
: 1.w, : 1.w,
), ),
boxShadow: [ boxShadow: [
if (isWinner)
BoxShadow( BoxShadow(
color: Colors.black color: scheme
.withOpacity(0.4), .primary
blurRadius: isWinner .withOpacity(
? 14.r 0.45),
: 6.r, blurRadius: 16.r,
offset: Offset(0, 3.h), offset: Offset(
0, 4.h),
), ),
], ],
), ),
@ -778,25 +802,49 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
name.length > 22 name.length > 22
? '${name.substring(0, 22)}' ? '${name.substring(0, 22)}'
: name, : name,
style: TextStyle( style: theme
fontSize: fontSize, .textTheme
fontWeight: weight, .titleMedium
color: Colors.white, ?.copyWith(
letterSpacing: 0.6, fontSize: isWinner
shadows: [ ? 21.sp
: isCenterHighlight
? 18.sp
: 16.sp,
fontWeight: isWinner
? FontWeight.w900
: FontWeight
.w700,
color: isWinner
? d
.winnerRowText
: d.idleRowText,
letterSpacing: 0.2,
shadows: theme
.brightness ==
Brightness
.dark
? [
Shadow( Shadow(
color: Colors.black color: Colors
.withOpacity(0.5), .black
blurRadius: 4.r, .withOpacity(
0.45),
blurRadius:
3,
offset: offset:
const Offset(1.5, 1.5), const Offset(
0,
1),
), ),
], ]
: null,
), ),
textAlign: TextAlign.center, textAlign:
TextAlign.center,
maxLines: 1, maxLines: 1,
overflow: overflow: TextOverflow
TextOverflow.ellipsis, .ellipsis,
), ),
), ),
), ),
@ -808,6 +856,40 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
); );
}, },
), ),
Positioned(
top: rowH * 2.5,
left: 8.w,
right: 8.w,
height: 1,
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.transparent,
scheme.tertiary.withOpacity(0.45),
Colors.transparent,
],
),
),
),
),
Positioned(
top: rowH * 4.5,
left: 8.w,
right: 8.w,
height: 1,
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.transparent,
scheme.tertiary.withOpacity(0.45),
Colors.transparent,
],
),
),
),
),
if (_isComplete) if (_isComplete)
Positioned( Positioned(
top: rowH * 3, top: rowH * 3,
@ -817,15 +899,17 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
child: IgnorePointer( child: IgnorePointer(
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.r), borderRadius:
BorderRadius.circular(12.r),
border: Border.all( border: Border.all(
color: Colors.amber.shade400, color: d.winnerFrameColor,
width: 2.5.w, width: 2.5.w,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.amber.withOpacity(0.35), color: scheme.tertiary
blurRadius: 14.r, .withOpacity(0.35),
blurRadius: 18.r,
spreadRadius: 0.5, spreadRadius: 0.5,
), ),
], ],
@ -840,35 +924,29 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
), ),
), ),
), ),
SizedBox(height: 16.h), ),
),
SizedBox(height: 14.h),
if (_isComplete && winnerName != null) if (_isComplete && winnerName != null)
Container( Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h), padding: EdgeInsets.symmetric(
decoration: BoxDecoration( horizontal: 16.w, vertical: 16.h),
gradient: LinearGradient( decoration: d.winnerSummaryDecoration(),
colors: [
Colors.green.shade50,
Colors.green.shade100.withOpacity(0.85),
],
),
borderRadius: BorderRadius.circular(14.r),
border: Border.all(color: Colors.green.shade200, width: 1.5),
),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.emoji_events, color: Colors.amber.shade700, size: 26.w), Icon(
SizedBox(width: 10.w), Icons.emoji_events_rounded,
color: d.winnerSummaryIcon,
size: 28.w,
),
SizedBox(width: 12.w),
Flexible( Flexible(
child: Text( child: Text(
winnerName, winnerName,
style: TextStyle( style: d.winnerSummaryTitle(theme.textTheme)
fontSize: 19.sp, .copyWith(fontSize: 18.sp),
fontWeight: FontWeight.w800,
color: Colors.green.shade900,
height: 1.2,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -881,6 +959,7 @@ class _SlotMachineDrawAnimationState extends State<SlotMachineDrawAnimation>
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../core/themes/draw_slot_theme.dart';
import 'alternative_draw_animations.dart'; import 'alternative_draw_animations.dart';
class DrawAnimationSelector extends StatefulWidget { class DrawAnimationSelector extends StatefulWidget {
@ -47,97 +48,101 @@ class _DrawAnimationSelectorState extends State<DrawAnimationSelector> {
}); });
} }
Widget _buildAnimationSelector() { Widget _buildAnimationSelector(BuildContext context) {
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final d = DrawSlotTheming(scheme, theme.brightness);
return Container( return Container(
padding: EdgeInsets.all(24.w), padding: EdgeInsets.fromLTRB(22.w, 26.h, 22.w, 24.h),
decoration: BoxDecoration( decoration: d.introCardDecoration(),
color: Colors.white,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10.r,
offset: Offset(0, 5.h),
),
],
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [ children: [
Container( Container(
width: 72.w, width: 56.w,
height: 72.w, height: 56.w,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.purple.shade50, color: scheme.primaryContainer.withOpacity(
borderRadius: BorderRadius.circular(20.r), theme.brightness == Brightness.dark ? 0.35 : 0.65),
borderRadius: BorderRadius.circular(16.r),
), ),
child: Icon( child: Icon(
Icons.casino, Icons.auto_awesome_rounded,
color: Colors.purple.shade600, color: scheme.primary,
size: 36.w, size: 28.w,
), ),
), ),
SizedBox(height: 16.h), SizedBox(width: 16.w),
Text( Expanded(
'Slot Machine Draw', child: Column(
style: TextStyle( crossAxisAlignment: CrossAxisAlignment.start,
fontSize: 22.sp,
fontWeight: FontWeight.w700,
color: Colors.grey.shade800,
),
textAlign: TextAlign.center,
),
SizedBox(height: 12.h),
Text(
'Our signature animation for dramatic, high-energy winner reveals.',
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey.shade600,
height: 1.4,
),
textAlign: TextAlign.center,
),
SizedBox(height: 16.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
decoration: BoxDecoration(
color: Colors.purple.shade50,
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
'Members in draw: ${widget.members.length}',
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w600,
color: Colors.purple.shade600,
),
),
),
SizedBox(height: 24.h),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _startDraw,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple.shade600,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.play_arrow, size: 20.w),
SizedBox(width: 8.w),
Text( Text(
'Start Slot Machine', 'Monthly draw',
style: TextStyle(fontSize: 16.sp), style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w800,
letterSpacing: -0.2,
),
),
SizedBox(height: 4.h),
Text(
'Fair random pick with a clear on-screen reveal.',
style: theme.textTheme.bodyMedium?.copyWith(
height: 1.35,
),
), ),
], ],
), ),
), ),
],
),
SizedBox(height: 20.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 12.h),
decoration: BoxDecoration(
color: scheme.surfaceContainerHighest.withOpacity(
theme.brightness == Brightness.dark ? 0.5 : 1),
borderRadius: BorderRadius.circular(14.r),
border: Border.all(color: scheme.outlineVariant),
),
child: Row(
children: [
Icon(
Icons.people_alt_rounded,
size: 20.w,
color: scheme.primary,
),
SizedBox(width: 10.w),
Expanded(
child: Text(
'${widget.members.length} eligible members in this draw',
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
],
),
),
SizedBox(height: 22.h),
FilledButton.icon(
onPressed: _startDraw,
icon: Icon(Icons.play_arrow_rounded, size: 22.w),
label: Text(
'Start draw',
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w700,
),
),
style: FilledButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
),
), ),
], ],
), ),
@ -153,7 +158,7 @@ class _DrawAnimationSelectorState extends State<DrawAnimationSelector> {
width: maxW, width: maxW,
child: Column( child: Column(
children: [ children: [
if (!_isDrawStarted) _buildAnimationSelector(), if (!_isDrawStarted) _buildAnimationSelector(context),
if (_isDrawStarted) ...[ if (_isDrawStarted) ...[
SlotMachineDrawAnimation( SlotMachineDrawAnimation(
members: widget.members, members: widget.members,
@ -167,29 +172,21 @@ class _DrawAnimationSelectorState extends State<DrawAnimationSelector> {
if (widget.allowReplay) if (widget.allowReplay)
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: OutlinedButton( child: OutlinedButton.icon(
onPressed: _resetDraw, onPressed: _resetDraw,
icon: Icon(Icons.replay_rounded, size: 20.w),
label: Text(
'Run again',
style: Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w700,
),
),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 12.h), padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r), borderRadius: BorderRadius.circular(16.r),
), ),
), ),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.refresh, size: 18.w, color: Colors.purple.shade600),
SizedBox(width: 6.w),
Text(
'Spin Again',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.purple.shade600,
),
),
],
),
), ),
), ),
], ],