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,47 +550,63 @@ _Congratulations to the winner!_
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
); );
} catch (e) { } catch (e) {
if (mounted) Get.back(); if (mounted) {
try {
Get.back();
} catch (_) {}
}
Get.back(result: false); if (mounted) {
Get.snackbar(
Get.snackbar( 'Error',
'Error', 'Failed to save draw result: ${e.toString()}',
'Failed to save draw result: ${e.toString()}', backgroundColor: Colors.red,
backgroundColor: Colors.red, colorText: Colors.white,
colorText: Colors.white, duration: const Duration(seconds: 4),
duration: const Duration(seconds: 4), );
); }
} }
} }
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: [
url, Expanded(child: const Text('Public result link')),
style: TextStyle(fontSize: 13.sp), IconButton(
tooltip: 'Close',
onPressed: () => Navigator.of(ctx).pop(),
icon: const Icon(Icons.close),
),
],
),
content: SingleChildScrollView(
child: SelectableText(
url,
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
icon: Icon( ? 'Done'
Icons.close, : (_isComplete
color: Colors.white.withOpacity(0.8), ? 'Close'
size: 28.w, : 'Cancel draw'),
), icon: Icon(
onPressed: () async { _drawRecorded
final result = await showDialog<bool>( ? Icons.check_circle_outline_rounded
context: context, : Icons.close_rounded,
builder: (context) => AlertDialog( color: d.pageOnText,
title: const Text('Cancel Draw?'), size: 28.w,
content: const Text('Are you sure you want to cancel the draw?'), ),
actions: [ onPressed: () => _handleLeaveIntent(),
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,51 +760,60 @@ _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(
decoration: BoxDecoration( horizontal: 12.w,
color: Colors.white.withOpacity(0.1), vertical: 14.h,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: Colors.white.withOpacity(0.2),
),
), ),
child: Column( decoration: BoxDecoration(
color: d.chipFill,
borderRadius: BorderRadius.circular(18.r),
border: Border.all(color: d.chipBorder),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
Row( _buildInfoItem(
mainAxisAlignment: MainAxisAlignment.spaceAround, d,
children: [ 'In this draw',
_buildInfoItem( '${widget.eligibleMembers.length}',
'Eligible Members', Icons.how_to_reg_rounded,
'${widget.eligibleMembers.length}', ),
Icons.people, Container(
), width: 1,
Container( height: 36.h,
width: 1, color: d.chipBorder.withOpacity(0.6),
height: 30.h, ),
color: Colors.white.withOpacity(0.3), _buildInfoItem(
), d,
_buildInfoItem( 'Group size',
'Total Members', '${widget.group.maxMembers}',
'${widget.group.maxMembers}', Icons.groups_rounded,
Icons.group,
),
],
), ),
], ],
), ),
), ),
SizedBox(height: 12.h), SizedBox(height: 14.h),
Text( Row(
'🎲 Provably Fair Draw', mainAxisAlignment: MainAxisAlignment.center,
style: TextStyle( children: [
fontSize: 14.sp, Icon(
color: Colors.white.withOpacity(0.7), Icons.verified_user_rounded,
fontWeight: FontWeight.w500, size: 16.w,
), color: theme.colorScheme.tertiary,
),
SizedBox(width: 8.w),
Text(
'Provably fair · transparent draw',
style: theme.textTheme.bodyMedium?.copyWith(
color: d.pageMuted,
fontWeight: FontWeight.w600,
),
),
],
), ),
], ],
), ),
@ -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,240 +650,313 @@ 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(
child: ConstrainedBox( label: 'Slot machine draw',
constraints: BoxConstraints(maxWidth: maxW.clamp(280.w, 400.w)), child: Center(
child: SizedBox( child: ConstrainedBox(
width: double.infinity, constraints: BoxConstraints(maxWidth: maxW.clamp(280.w, 400.w)),
height: 480.h.clamp(380.h, 560.h), child: SizedBox(
child: Column( width: double.infinity,
children: [ height: 480.h.clamp(380.h, 560.h),
Expanded( child: Column(
child: Container( crossAxisAlignment: CrossAxisAlignment.stretch,
width: double.infinity, children: [
decoration: BoxDecoration( if (_isAnimating)
gradient: LinearGradient( Padding(
colors: [ padding: EdgeInsets.only(bottom: 10.h),
Colors.grey.shade900, child: Row(
Colors.grey.shade800, 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,
),
),
], ],
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),
),
],
), ),
Expanded(
child: Container( child: Container(
margin: EdgeInsets.all(18.w), width: double.infinity,
decoration: BoxDecoration( decoration: d.slotChassisDecoration(),
color: Colors.black, child: Padding(
borderRadius: BorderRadius.circular(12.r), padding: EdgeInsets.all(14.w),
), child: DecoratedBox(
child: LayoutBuilder( decoration: BoxDecoration(
builder: (context, constraints) { color: d.slotInnerWell,
final rowH = constraints.maxHeight / 7; borderRadius: BorderRadius.circular(14.r),
return Stack( border: Border.all(
clipBehavior: Clip.hardEdge, color: scheme.outline.withOpacity(0.28),
children: [ ),
AnimatedBuilder( ),
animation: _slotAnimation, child: ClipRRect(
builder: (context, child) { borderRadius: BorderRadius.circular(13.r),
return Column( child: LayoutBuilder(
children: List.generate(7, (index) { builder: (context, constraints) {
final displayIndex = final rowH = constraints.maxHeight / 7;
index < _displayNames.length ? index : 0; return Stack(
final name = _displayNames[displayIndex]; clipBehavior: Clip.hardEdge,
final isWinner = _isComplete && index == 3; fit: StackFit.expand,
final isCenterHighlight = children: [
_isAnimating && index == 3; AnimatedBuilder(
animation: _slotAnimation,
builder: (context, child) {
return Column(
children:
List.generate(7, (index) {
final displayIndex = index <
_displayNames.length
? index
: 0;
final name =
_displayNames[displayIndex];
final isWinner =
_isComplete && index == 3;
final isCenterHighlight =
_isAnimating && index == 3;
return Expanded( return Expanded(
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) {
: isCenterHighlight rowColors = d.rowColorsWinner();
? 20.sp } else if (isCenterHighlight) {
: 18.sp; rowColors =
final FontWeight weight = isWinner d.rowColorsSpinning();
? FontWeight.w900 } else {
: isCenterHighlight rowColors = d.rowColorsIdle();
? 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( final borderColor = isWinner
duration: const Duration( ? scheme.tertiary
milliseconds: 220), .withOpacity(0.9)
curve: Curves.easeInOut, : isCenterHighlight
transform: Matrix4.identity() ? scheme.primary
..scale(scale), .withOpacity(0.55)
child: Container( : scheme.outline
width: double.infinity, .withOpacity(0.22);
margin: EdgeInsets.symmetric(
vertical: 6.h, return AnimatedContainer(
horizontal: 12.w, duration: const Duration(
), milliseconds: 220),
decoration: BoxDecoration( curve: Curves.easeInOut,
gradient: LinearGradient( transform: Matrix4.identity()
colors: colors, ..scale(scale),
begin: Alignment.topLeft, child: Container(
end: Alignment.bottomRight, width: double.infinity,
), margin:
borderRadius: EdgeInsets.symmetric(
BorderRadius.circular(10.r), vertical: 4.h,
border: Border.all( horizontal: 10.w,
color: Colors.white.withOpacity( ),
isWinner || decoration: BoxDecoration(
isCenterHighlight gradient:
? 0.7 LinearGradient(
: 0.15), colors: rowColors,
width: isWinner || begin: Alignment
isCenterHighlight .centerLeft,
? 2.w end: Alignment
: 1.w, .centerRight,
),
boxShadow: [
BoxShadow(
color: Colors.black
.withOpacity(0.4),
blurRadius: isWinner
? 14.r
: 6.r,
offset: Offset(0, 3.h),
),
],
),
child: Center(
child: Text(
name.length > 22
? '${name.substring(0, 22)}'
: name,
style: TextStyle(
fontSize: fontSize,
fontWeight: weight,
color: Colors.white,
letterSpacing: 0.6,
shadows: [
Shadow(
color: Colors.black
.withOpacity(0.5),
blurRadius: 4.r,
offset:
const Offset(1.5, 1.5),
), ),
], borderRadius:
BorderRadius
.circular(12.r),
border: Border.all(
color: borderColor,
width: isWinner
? 2.w
: 1.w,
),
boxShadow: [
if (isWinner)
BoxShadow(
color: scheme
.primary
.withOpacity(
0.45),
blurRadius: 16.r,
offset: Offset(
0, 4.h),
),
],
),
child: Center(
child: Text(
name.length > 22
? '${name.substring(0, 22)}'
: name,
style: theme
.textTheme
.titleMedium
?.copyWith(
fontSize: isWinner
? 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(
color: Colors
.black
.withOpacity(
0.45),
blurRadius:
3,
offset:
const Offset(
0,
1),
),
]
: null,
),
textAlign:
TextAlign.center,
maxLines: 1,
overflow: TextOverflow
.ellipsis,
),
),
), ),
textAlign: TextAlign.center, );
maxLines: 1, },
overflow:
TextOverflow.ellipsis,
),
),
), ),
); );
}, }),
), );
); },
}), ),
); Positioned(
}, top: rowH * 2.5,
), left: 8.w,
if (_isComplete) right: 8.w,
Positioned( height: 1,
top: rowH * 3, child: DecoratedBox(
left: 0, decoration: BoxDecoration(
right: 0, gradient: LinearGradient(
height: rowH, colors: [
child: IgnorePointer( Colors.transparent,
child: DecoratedBox( scheme.tertiary.withOpacity(0.45),
decoration: BoxDecoration( Colors.transparent,
borderRadius: BorderRadius.circular(10.r), ],
border: Border.all(
color: Colors.amber.shade400,
width: 2.5.w,
),
boxShadow: [
BoxShadow(
color: Colors.amber.withOpacity(0.35),
blurRadius: 14.r,
spreadRadius: 0.5,
), ),
], ),
), ),
), ),
), Positioned(
), top: rowH * 4.5,
], left: 8.w,
); right: 8.w,
}, height: 1,
), child: DecoratedBox(
), decoration: BoxDecoration(
), gradient: LinearGradient(
), colors: [
SizedBox(height: 16.h), Colors.transparent,
if (_isComplete && winnerName != null) scheme.tertiary.withOpacity(0.45),
Container( Colors.transparent,
width: double.infinity, ],
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h), ),
decoration: BoxDecoration( ),
gradient: LinearGradient( ),
colors: [ ),
Colors.green.shade50, if (_isComplete)
Colors.green.shade100.withOpacity(0.85), Positioned(
], top: rowH * 3,
), left: 0,
borderRadius: BorderRadius.circular(14.r), right: 0,
border: Border.all(color: Colors.green.shade200, width: 1.5), height: rowH,
), child: IgnorePointer(
child: Row( child: DecoratedBox(
mainAxisAlignment: MainAxisAlignment.center, decoration: BoxDecoration(
children: [ borderRadius:
Icon(Icons.emoji_events, color: Colors.amber.shade700, size: 26.w), BorderRadius.circular(12.r),
SizedBox(width: 10.w), border: Border.all(
Flexible( color: d.winnerFrameColor,
child: Text( width: 2.5.w,
winnerName, ),
style: TextStyle( boxShadow: [
fontSize: 19.sp, BoxShadow(
fontWeight: FontWeight.w800, color: scheme.tertiary
color: Colors.green.shade900, .withOpacity(0.35),
height: 1.2, blurRadius: 18.r,
spreadRadius: 0.5,
),
],
),
),
),
),
],
);
},
), ),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
), ),
], ),
), ),
), ),
], SizedBox(height: 14.h),
if (_isComplete && winnerName != null)
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 16.w, vertical: 16.h),
decoration: d.winnerSummaryDecoration(),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.emoji_events_rounded,
color: d.winnerSummaryIcon,
size: 28.w,
),
SizedBox(width: 12.w),
Flexible(
child: Text(
winnerName,
style: d.winnerSummaryTitle(theme.textTheme)
.copyWith(fontSize: 18.sp),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
],
),
), ),
), ),
), ),

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,95 +48,99 @@ 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: [ children: [
Container( Row(
width: 72.w, children: [
height: 72.w, Container(
decoration: BoxDecoration( width: 56.w,
color: Colors.purple.shade50, height: 56.w,
borderRadius: BorderRadius.circular(20.r), decoration: BoxDecoration(
), color: scheme.primaryContainer.withOpacity(
child: Icon( theme.brightness == Brightness.dark ? 0.35 : 0.65),
Icons.casino, borderRadius: BorderRadius.circular(16.r),
color: Colors.purple.shade600, ),
size: 36.w, child: Icon(
), Icons.auto_awesome_rounded,
), color: scheme.primary,
SizedBox(height: 16.h), size: 28.w,
Text(
'Slot Machine Draw',
style: TextStyle(
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( SizedBox(width: 16.w),
mainAxisAlignment: MainAxisAlignment.center, Expanded(
children: [ child: Column(
Icon(Icons.play_arrow, size: 20.w), crossAxisAlignment: CrossAxisAlignment.start,
SizedBox(width: 8.w), children: [
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,28 +172,20 @@ 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,
style: OutlinedButton.styleFrom( icon: Icon(Icons.replay_rounded, size: 20.w),
padding: EdgeInsets.symmetric(vertical: 12.h), label: Text(
shape: RoundedRectangleBorder( 'Run again',
borderRadius: BorderRadius.circular(8.r), style: Theme.of(context).textTheme.labelLarge?.copyWith(
), fontWeight: FontWeight.w700,
),
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,
), ),
), ),
], style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 14.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
), ),
), ),
), ),