fixed animations
This commit is contained in:
parent
5c284a3698
commit
1c861e1f4b
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue