830 lines
26 KiB
Dart
830 lines
26 KiB
Dart
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:gap/gap.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:island/models/wallet.dart';
|
|
import 'package:island/pods/network.dart';
|
|
import 'package:island/screens/wallet.dart';
|
|
import 'package:island/services/time.dart';
|
|
import 'package:island/widgets/alert.dart';
|
|
import 'package:island/widgets/content/sheet.dart';
|
|
import 'package:island/widgets/payment/payment_overlay.dart';
|
|
import 'package:material_symbols_icons/symbols.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
import 'package:styled_widget/styled_widget.dart';
|
|
|
|
part 'lottery.g.dart';
|
|
|
|
@riverpod
|
|
Future<List<SnLotteryTicket>> lotteryTickets(
|
|
Ref ref, {
|
|
int offset = 0,
|
|
int take = 20,
|
|
}) async {
|
|
final client = ref.watch(apiClientProvider);
|
|
final resp = await client.get('/pass/lotteries?offset=$offset&take=$take');
|
|
return (resp.data as List).map((e) => SnLotteryTicket.fromJson(e)).toList();
|
|
}
|
|
|
|
@riverpod
|
|
Future<List<SnLotteryRecord>> lotteryRecords(
|
|
Ref ref, {
|
|
int offset = 0,
|
|
int take = 20,
|
|
}) async {
|
|
final client = ref.watch(apiClientProvider);
|
|
final resp = await client.get(
|
|
'/pass/lotteries/records?offset=$offset&take=$take',
|
|
);
|
|
return (resp.data as List).map((e) => SnLotteryRecord.fromJson(e)).toList();
|
|
}
|
|
|
|
class LotteryTab extends StatelessWidget {
|
|
const LotteryTab({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return DefaultTabController(
|
|
length: 2,
|
|
child: Column(
|
|
children: [
|
|
TabBar(
|
|
tabs: [Tab(text: 'myTickets'.tr()), Tab(text: 'drawHistory'.tr())],
|
|
),
|
|
Expanded(
|
|
child: TabBarView(
|
|
children: [LotteryTicketsList(), LotteryRecordsList()],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class LotteryTicketsList extends HookConsumerWidget {
|
|
const LotteryTicketsList({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final tickets = ref.watch(lotteryTicketsProvider());
|
|
|
|
return tickets.when(
|
|
data: (ticketsList) {
|
|
if (ticketsList.isEmpty) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Symbols.casino,
|
|
size: 48,
|
|
color: Theme.of(context).colorScheme.outline,
|
|
),
|
|
const Gap(16),
|
|
Text(
|
|
'noLotteryTickets'.tr(),
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
const Gap(8),
|
|
Text(
|
|
'buyYourFirstTicket'.tr(),
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const Gap(16),
|
|
FilledButton.icon(
|
|
onPressed: () => _showLotteryPurchaseSheet(context, ref),
|
|
icon: const Icon(Symbols.add),
|
|
label: Text('buyTicket'.tr()),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
return Column(
|
|
children: [
|
|
Expanded(
|
|
child: ListView.builder(
|
|
padding: const EdgeInsets.all(16),
|
|
itemCount: ticketsList.length,
|
|
itemBuilder: (context, index) {
|
|
final ticket = ticketsList[index];
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Symbols.confirmation_number,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
),
|
|
const Gap(8),
|
|
Expanded(
|
|
child: Text(ticket.createdAt.formatSystem()),
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
vertical: 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: _getLotteryStatusColor(
|
|
context,
|
|
ticket.drawStatus,
|
|
).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
_getLotteryStatusText(ticket.drawStatus),
|
|
style: TextStyle(
|
|
color: _getLotteryStatusColor(
|
|
context,
|
|
ticket.drawStatus,
|
|
),
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const Gap(8),
|
|
_buildTicketNumbersDisplay(context, ticket),
|
|
const Gap(8),
|
|
Row(
|
|
spacing: 6,
|
|
children: [
|
|
const Icon(Symbols.asterisk, size: 18),
|
|
Text('multiplier').tr().fontSize(13),
|
|
Text(
|
|
'·',
|
|
).fontWeight(FontWeight.w900).fontSize(13),
|
|
Text(
|
|
'${ticket.multiplier}x',
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
).fontSize(13),
|
|
],
|
|
).opacity(0.75),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: FilledButton.icon(
|
|
onPressed: () => _showLotteryPurchaseSheet(context, ref),
|
|
icon: const Icon(Symbols.add),
|
|
label: Text('buyTicket'.tr()),
|
|
style: FilledButton.styleFrom(
|
|
minimumSize: const Size(double.infinity, 48),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
|
);
|
|
}
|
|
|
|
Future<void> _showLotteryPurchaseSheet(
|
|
BuildContext context,
|
|
WidgetRef ref,
|
|
) async {
|
|
final result = await showModalBottomSheet<Map<String, dynamic>>(
|
|
context: context,
|
|
useRootNavigator: true,
|
|
isScrollControlled: true,
|
|
builder: (context) => const LotteryPurchaseSheet(),
|
|
);
|
|
|
|
if (result != null && context.mounted) {
|
|
await _handleLotteryPurchase(context, ref, result);
|
|
}
|
|
}
|
|
|
|
Future<void> _handleLotteryPurchase(
|
|
BuildContext context,
|
|
WidgetRef ref,
|
|
Map<String, dynamic> purchaseData,
|
|
) async {
|
|
final client = ref.read(apiClientProvider);
|
|
try {
|
|
showLoadingModal(context);
|
|
|
|
// The lottery API creates the order for us
|
|
final orderResponse = await client.post(
|
|
'/pass/lotteries',
|
|
data: purchaseData,
|
|
);
|
|
|
|
if (context.mounted) hideLoadingModal(context);
|
|
|
|
final order = SnWalletOrder.fromJson(orderResponse.data);
|
|
|
|
// Show payment overlay
|
|
if (context.mounted) {
|
|
final completedOrder = await PaymentOverlay.show(
|
|
context: context,
|
|
order: order,
|
|
);
|
|
|
|
if (completedOrder != null) {
|
|
// Payment successful, refresh data
|
|
ref.invalidate(lotteryTicketsProvider);
|
|
ref.invalidate(walletCurrentProvider);
|
|
if (context.mounted) {
|
|
showSnackBar('ticketPurchasedSuccessfully'.tr());
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (context.mounted) hideLoadingModal(context);
|
|
showErrorAlert(err);
|
|
}
|
|
}
|
|
|
|
String _getLotteryStatusText(int status) {
|
|
switch (status) {
|
|
case 0:
|
|
return 'pending'.tr();
|
|
case 1:
|
|
return 'drawn'.tr();
|
|
case 2:
|
|
return 'won'.tr();
|
|
case 3:
|
|
return 'lost'.tr();
|
|
default:
|
|
return 'unknown'.tr();
|
|
}
|
|
}
|
|
|
|
Color _getLotteryStatusColor(BuildContext context, int status) {
|
|
switch (status) {
|
|
case 0:
|
|
return Colors.blue;
|
|
case 1:
|
|
return Colors.orange;
|
|
case 2:
|
|
return Colors.green;
|
|
case 3:
|
|
return Colors.red;
|
|
default:
|
|
return Theme.of(context).colorScheme.primary;
|
|
}
|
|
}
|
|
|
|
Widget _buildTicketNumbersDisplay(
|
|
BuildContext context,
|
|
SnLotteryTicket ticket,
|
|
) {
|
|
final numbers = <Widget>[];
|
|
|
|
// Check if any numbers matched
|
|
bool hasAnyMatch =
|
|
ticket.matchedRegionOneNumbers != null &&
|
|
ticket.matchedRegionOneNumbers!.isNotEmpty;
|
|
|
|
// Add region one numbers
|
|
for (final number in ticket.regionOneNumbers) {
|
|
final isMatched =
|
|
ticket.matchedRegionOneNumbers?.contains(number) ?? false;
|
|
if (isMatched) hasAnyMatch = true;
|
|
numbers.add(
|
|
_buildNumberWidget(
|
|
context,
|
|
number,
|
|
isMatched: isMatched,
|
|
allUnmatched: !hasAnyMatch && ticket.drawStatus >= 1,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Add region two number
|
|
final isSpecialMatched =
|
|
ticket.matchedRegionTwoNumber == ticket.regionTwoNumber;
|
|
if (isSpecialMatched) hasAnyMatch = true;
|
|
numbers.add(
|
|
_buildNumberWidget(
|
|
context,
|
|
ticket.regionTwoNumber,
|
|
isMatched: isSpecialMatched,
|
|
isSpecial: true,
|
|
allUnmatched: !hasAnyMatch && ticket.drawStatus >= 1,
|
|
),
|
|
);
|
|
|
|
return Wrap(
|
|
spacing: 6,
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
children: numbers,
|
|
);
|
|
}
|
|
|
|
Widget _buildNumberWidget(
|
|
BuildContext context,
|
|
int number, {
|
|
bool isMatched = false,
|
|
bool isSpecial = false,
|
|
bool allUnmatched = false,
|
|
}) {
|
|
Color backgroundColor;
|
|
Color textColor;
|
|
Color borderColor;
|
|
|
|
if (isMatched) {
|
|
backgroundColor = Colors.green;
|
|
textColor = Colors.white;
|
|
borderColor = Colors.green;
|
|
} else {
|
|
backgroundColor =
|
|
isSpecial
|
|
? Theme.of(context).colorScheme.secondary
|
|
: Theme.of(context).colorScheme.surface;
|
|
textColor =
|
|
isSpecial
|
|
? Theme.of(context).colorScheme.onSecondary
|
|
: Theme.of(context).colorScheme.onSurface;
|
|
borderColor =
|
|
isSpecial
|
|
? Theme.of(context).colorScheme.secondary
|
|
: Theme.of(context).colorScheme.outline.withOpacity(0.3);
|
|
|
|
// Blend with red if all numbers are unmatched
|
|
if (allUnmatched) {
|
|
backgroundColor = Color.alphaBlend(
|
|
Colors.red.withOpacity(0.3),
|
|
backgroundColor,
|
|
);
|
|
if (!isSpecial) {
|
|
textColor = Color.alphaBlend(Colors.red.withOpacity(0.5), textColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(
|
|
color: backgroundColor,
|
|
border: Border.all(color: borderColor),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
number.toString().padLeft(2, '0'),
|
|
style: TextStyle(
|
|
color: textColor,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class LotteryRecordsList extends HookConsumerWidget {
|
|
const LotteryRecordsList({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final records = ref.watch(lotteryRecordsProvider());
|
|
|
|
return records.when(
|
|
data: (recordsList) {
|
|
if (recordsList.isEmpty) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Symbols.history,
|
|
size: 48,
|
|
color: Theme.of(context).colorScheme.outline,
|
|
),
|
|
const Gap(16),
|
|
Text(
|
|
'noDrawHistory'.tr(),
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
return ListView.builder(
|
|
padding: const EdgeInsets.all(16),
|
|
itemCount: recordsList.length,
|
|
itemBuilder: (context, index) {
|
|
final record = recordsList[index];
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Symbols.celebration,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
),
|
|
const Gap(8),
|
|
Text(
|
|
DateFormat.yMd().format(record.drawDate),
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const Gap(8),
|
|
Text(
|
|
'${'winningNumbers'.tr()}: ${record.winningRegionOneNumbers.join(', ')}',
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
),
|
|
const Gap(4),
|
|
Text(
|
|
'${'specialNumber'.tr()}: ${record.winningRegionTwoNumber}',
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
),
|
|
const Gap(8),
|
|
Text(
|
|
'${'totalTickets'.tr()}: ${record.totalTickets}',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
const Gap(4),
|
|
Text(
|
|
'${'totalWinners'.tr()}: ${record.totalPrizesAwarded}',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
const Gap(4),
|
|
Text(
|
|
'${'prizePool'.tr()}: ${record.totalPrizeAmount.toStringAsFixed(2)} ${'walletCurrencyShortPoints'.tr()}',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
|
);
|
|
}
|
|
}
|
|
|
|
class LotteryPurchaseSheet extends StatefulWidget {
|
|
const LotteryPurchaseSheet({super.key});
|
|
|
|
@override
|
|
State<LotteryPurchaseSheet> createState() => _LotteryPurchaseSheetState();
|
|
}
|
|
|
|
class _LotteryPurchaseSheetState extends State<LotteryPurchaseSheet> {
|
|
final List<int> selectedNumbers = [];
|
|
int multiplier = 1;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final totalCost = 10.0 * multiplier; // Base cost of 10 ISP per ticket
|
|
|
|
return SheetScaffold(
|
|
titleText: 'buyLotteryTicket'.tr(),
|
|
child: Column(
|
|
children: [
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Number Selection Section
|
|
Text(
|
|
'selectNumbers'.tr(),
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
),
|
|
),
|
|
const Gap(8),
|
|
Text(
|
|
'select5UniqueNumbers'.tr(),
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
const Gap(4),
|
|
Text(
|
|
'The last selected number will be your special number.',
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: Theme.of(context).colorScheme.secondary,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const Gap(16),
|
|
|
|
// Number Grid
|
|
_buildNumberGrid(),
|
|
|
|
const Gap(16),
|
|
|
|
// Multiplier Section
|
|
Text(
|
|
'selectMultiplier'.tr(),
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
),
|
|
),
|
|
const Gap(16),
|
|
_buildMultiplierSelector(),
|
|
|
|
const Gap(16),
|
|
|
|
// Cost Summary
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text('baseCost'.tr()),
|
|
Text('10.00 ${'walletCurrencyShortPoints'.tr()}'),
|
|
],
|
|
),
|
|
if (multiplier > 1) ...[
|
|
const Gap(8),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'multiplier'.tr(
|
|
args: [multiplier.toString()],
|
|
),
|
|
),
|
|
Text(
|
|
'+ ${(10.0 * (multiplier - 1)).toStringAsFixed(2)} ${'walletCurrencyShortPoints'.tr()}',
|
|
),
|
|
],
|
|
),
|
|
],
|
|
const Divider(),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'totalCost'.tr(),
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
Text(
|
|
'${totalCost.toStringAsFixed(2)} ${'walletCurrencyShortPoints'.tr()}',
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const Gap(16),
|
|
|
|
// Prize Structure
|
|
Text(
|
|
'prizeStructure'.tr(),
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
),
|
|
),
|
|
const Gap(8),
|
|
_buildPrizeStructure(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Action Buttons
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: Text('cancel'.tr()),
|
|
),
|
|
),
|
|
const Gap(8),
|
|
Expanded(
|
|
child: FilledButton(
|
|
onPressed: _canPurchase ? _purchaseTicket : null,
|
|
child: Text('purchase'.tr()),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildNumberGrid() {
|
|
return GridView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 10,
|
|
crossAxisSpacing: 8,
|
|
mainAxisSpacing: 8,
|
|
),
|
|
itemCount: 100,
|
|
itemBuilder: (context, index) {
|
|
final number = index;
|
|
final isSelected = selectedNumbers.contains(number);
|
|
final isSpecialNumber =
|
|
selectedNumbers.isNotEmpty &&
|
|
selectedNumbers.last == number &&
|
|
selectedNumbers.length == 6;
|
|
|
|
return GestureDetector(
|
|
onTap: () => _toggleNumber(number),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color:
|
|
isSpecialNumber
|
|
? Theme.of(context).colorScheme.secondary
|
|
: isSelected
|
|
? Theme.of(context).colorScheme.primary
|
|
: Theme.of(context).colorScheme.surface,
|
|
border: Border.all(
|
|
color:
|
|
isSpecialNumber
|
|
? Theme.of(context).colorScheme.secondary
|
|
: isSelected
|
|
? Theme.of(context).colorScheme.primary
|
|
: Theme.of(
|
|
context,
|
|
).colorScheme.outline.withOpacity(0.3),
|
|
),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
number.toString().padLeft(2, '0'),
|
|
style: TextStyle(
|
|
color:
|
|
isSpecialNumber
|
|
? Theme.of(context).colorScheme.onSecondary
|
|
: isSelected
|
|
? Theme.of(context).colorScheme.onPrimary
|
|
: Theme.of(context).colorScheme.onSurface,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildMultiplierSelector() {
|
|
return TextFormField(
|
|
initialValue: multiplier.toString(),
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
labelText: 'multiplierLabel'.tr(),
|
|
prefixText: 'x',
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 12,
|
|
),
|
|
),
|
|
onChanged: (value) {
|
|
final parsed = int.tryParse(value);
|
|
if (parsed != null && parsed >= 1) {
|
|
setState(() => multiplier = parsed);
|
|
}
|
|
},
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Please enter a multiplier';
|
|
}
|
|
final parsed = int.tryParse(value);
|
|
if (parsed == null || parsed < 1 || parsed > 10) {
|
|
return 'Multiplier must be between 1 and 10';
|
|
}
|
|
return null;
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildPrizeStructure() {
|
|
// Base rewards for matched numbers (0-5)
|
|
final baseRewards = [0, 10, 100, 500, 1000, 10000];
|
|
|
|
final prizeStructure = <String, String>{};
|
|
|
|
// Generate prize structure for 0-5 matches with and without special
|
|
for (int matches = 5; matches >= 0; matches--) {
|
|
final baseReward = baseRewards[matches];
|
|
|
|
// With special number match (x10 multiplier)
|
|
final specialReward = baseReward * 10;
|
|
prizeStructure['$matches+Special'] = specialReward.toStringAsFixed(2);
|
|
|
|
// Without special number match
|
|
if (matches > 0) {
|
|
prizeStructure[matches.toString()] = baseReward.toStringAsFixed(2);
|
|
}
|
|
}
|
|
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
children:
|
|
prizeStructure.entries.map((entry) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
entry.key == '0+Special'
|
|
? 'specialOnly'.tr()
|
|
: entry.key.tr(),
|
|
),
|
|
Text(
|
|
'${entry.value} ${'walletCurrencyShortPoints'.tr()}',
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _toggleNumber(int number) {
|
|
setState(() {
|
|
if (selectedNumbers.contains(number)) {
|
|
selectedNumbers.remove(number);
|
|
} else if (selectedNumbers.length < 6) {
|
|
selectedNumbers.add(number);
|
|
}
|
|
});
|
|
}
|
|
|
|
bool get _canPurchase {
|
|
return selectedNumbers.length == 6;
|
|
}
|
|
|
|
Future<void> _purchaseTicket() async {
|
|
if (!_canPurchase) return;
|
|
|
|
// Sort all numbers except the last one (special number)
|
|
final regularNumbers = selectedNumbers.sublist(0, 5)..sort();
|
|
final specialNumber = selectedNumbers.last;
|
|
|
|
final purchaseData = {
|
|
'region_one_numbers': regularNumbers,
|
|
'region_two_number': specialNumber,
|
|
'multiplier': multiplier,
|
|
};
|
|
|
|
if (mounted) Navigator.of(context).pop(purchaseData);
|
|
}
|
|
}
|