Lotteries

This commit is contained in:
2025-10-25 00:18:08 +08:00
parent 327b4c04f1
commit cdf1413fe0
7 changed files with 1919 additions and 18 deletions

View File

@@ -1255,5 +1255,35 @@
"settingsDisableAnimation": "Disable Animation",
"addTag": "Add Tag",
"postFeaturedIn": "Post featured on {}",
"messageSentAt": "Sent at {}"
"messageSentAt": "Sent at {}",
"myTickets": "My Tickets",
"drawHistory": "Draw History",
"noLotteryTickets": "No lottery tickets yet",
"buyYourFirstTicket": "Buy your first lottery ticket to get started!",
"buyTicket": "Buy Ticket",
"ticketNumbers": "Numbers: {}, Special: {}",
"cost": "Cost",
"multiplier": "Multiplier",
"prizeWon": "Prize Won",
"pending": "Pending",
"drawn": "Drawn",
"won": "Won",
"lost": "Lost",
"noDrawHistory": "No draw history yet",
"buyLotteryTicket": "Buy Lottery Ticket",
"selectNumbers": "Select Numbers",
"select5UniqueNumbers": "Select 5 unique numbers (0-99)",
"selectSpecialNumber": "Select Special Number",
"selectMultiplier": "Select Multiplier",
"baseCost": "Base Cost",
"totalCost": "Total Cost",
"prizeStructure": "Prize Structure",
"enterPinToConfirmPurchase": "Enter your PIN to confirm purchase",
"ticketPurchasedSuccessfully": "Ticket purchased successfully!",
"winningNumbers": "Winning Numbers",
"specialNumber": "Special Number",
"totalTickets": "Total Tickets",
"totalWinners": "Total Winners",
"prizePool": "Prize Pool",
"enterPinToConfirmPayment": "Enter your PIN code to confirm payment"
}

View File

@@ -211,3 +211,44 @@ sealed class SnWalletFundRecipient with _$SnWalletFundRecipient {
factory SnWalletFundRecipient.fromJson(Map<String, dynamic> json) =>
_$SnWalletFundRecipientFromJson(json);
}
@freezed
sealed class SnLotteryTicket with _$SnLotteryTicket {
const factory SnLotteryTicket({
required String id,
required String accountId,
required SnAccount? account,
required List<int> regionOneNumbers,
required int regionTwoNumber,
required int multiplier,
required int drawStatus,
required DateTime? drawDate,
required List<int>? matchedRegionOneNumbers,
required int? matchedRegionTwoNumber,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnLotteryTicket;
factory SnLotteryTicket.fromJson(Map<String, dynamic> json) =>
_$SnLotteryTicketFromJson(json);
}
@freezed
sealed class SnLotteryRecord with _$SnLotteryRecord {
const factory SnLotteryRecord({
required String id,
required DateTime drawDate,
required List<int> winningRegionOneNumbers,
required int winningRegionTwoNumber,
required int totalTickets,
required int totalPrizesAwarded,
required double totalPrizeAmount,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnLotteryRecord;
factory SnLotteryRecord.fromJson(Map<String, dynamic> json) =>
_$SnLotteryRecordFromJson(json);
}

View File

@@ -3184,4 +3184,625 @@ $SnAccountCopyWith<$Res>? get recipientAccount {
}
}
/// @nodoc
mixin _$SnLotteryTicket {
String get id; String get accountId; SnAccount? get account; List<int> get regionOneNumbers; int get regionTwoNumber; int get multiplier; int get drawStatus; DateTime? get drawDate; List<int>? get matchedRegionOneNumbers; int? get matchedRegionTwoNumber; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnLotteryTicket
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnLotteryTicketCopyWith<SnLotteryTicket> get copyWith => _$SnLotteryTicketCopyWithImpl<SnLotteryTicket>(this as SnLotteryTicket, _$identity);
/// Serializes this SnLotteryTicket to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnLotteryTicket&&(identical(other.id, id) || other.id == id)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&const DeepCollectionEquality().equals(other.regionOneNumbers, regionOneNumbers)&&(identical(other.regionTwoNumber, regionTwoNumber) || other.regionTwoNumber == regionTwoNumber)&&(identical(other.multiplier, multiplier) || other.multiplier == multiplier)&&(identical(other.drawStatus, drawStatus) || other.drawStatus == drawStatus)&&(identical(other.drawDate, drawDate) || other.drawDate == drawDate)&&const DeepCollectionEquality().equals(other.matchedRegionOneNumbers, matchedRegionOneNumbers)&&(identical(other.matchedRegionTwoNumber, matchedRegionTwoNumber) || other.matchedRegionTwoNumber == matchedRegionTwoNumber)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,accountId,account,const DeepCollectionEquality().hash(regionOneNumbers),regionTwoNumber,multiplier,drawStatus,drawDate,const DeepCollectionEquality().hash(matchedRegionOneNumbers),matchedRegionTwoNumber,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnLotteryTicket(id: $id, accountId: $accountId, account: $account, regionOneNumbers: $regionOneNumbers, regionTwoNumber: $regionTwoNumber, multiplier: $multiplier, drawStatus: $drawStatus, drawDate: $drawDate, matchedRegionOneNumbers: $matchedRegionOneNumbers, matchedRegionTwoNumber: $matchedRegionTwoNumber, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $SnLotteryTicketCopyWith<$Res> {
factory $SnLotteryTicketCopyWith(SnLotteryTicket value, $Res Function(SnLotteryTicket) _then) = _$SnLotteryTicketCopyWithImpl;
@useResult
$Res call({
String id, String accountId, SnAccount? account, List<int> regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List<int>? matchedRegionOneNumbers, int? matchedRegionTwoNumber, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
$SnAccountCopyWith<$Res>? get account;
}
/// @nodoc
class _$SnLotteryTicketCopyWithImpl<$Res>
implements $SnLotteryTicketCopyWith<$Res> {
_$SnLotteryTicketCopyWithImpl(this._self, this._then);
final SnLotteryTicket _self;
final $Res Function(SnLotteryTicket) _then;
/// Create a copy of SnLotteryTicket
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? accountId = null,Object? account = freezed,Object? regionOneNumbers = null,Object? regionTwoNumber = null,Object? multiplier = null,Object? drawStatus = null,Object? drawDate = freezed,Object? matchedRegionOneNumbers = freezed,Object? matchedRegionTwoNumber = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount?,regionOneNumbers: null == regionOneNumbers ? _self.regionOneNumbers : regionOneNumbers // ignore: cast_nullable_to_non_nullable
as List<int>,regionTwoNumber: null == regionTwoNumber ? _self.regionTwoNumber : regionTwoNumber // ignore: cast_nullable_to_non_nullable
as int,multiplier: null == multiplier ? _self.multiplier : multiplier // ignore: cast_nullable_to_non_nullable
as int,drawStatus: null == drawStatus ? _self.drawStatus : drawStatus // ignore: cast_nullable_to_non_nullable
as int,drawDate: freezed == drawDate ? _self.drawDate : drawDate // ignore: cast_nullable_to_non_nullable
as DateTime?,matchedRegionOneNumbers: freezed == matchedRegionOneNumbers ? _self.matchedRegionOneNumbers : matchedRegionOneNumbers // ignore: cast_nullable_to_non_nullable
as List<int>?,matchedRegionTwoNumber: freezed == matchedRegionTwoNumber ? _self.matchedRegionTwoNumber : matchedRegionTwoNumber // ignore: cast_nullable_to_non_nullable
as int?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
/// Create a copy of SnLotteryTicket
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
}
/// Adds pattern-matching-related methods to [SnLotteryTicket].
extension SnLotteryTicketPatterns on SnLotteryTicket {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnLotteryTicket value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnLotteryTicket() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnLotteryTicket value) $default,){
final _that = this;
switch (_that) {
case _SnLotteryTicket():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnLotteryTicket value)? $default,){
final _that = this;
switch (_that) {
case _SnLotteryTicket() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String accountId, SnAccount? account, List<int> regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List<int>? matchedRegionOneNumbers, int? matchedRegionTwoNumber, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnLotteryTicket() when $default != null:
return $default(_that.id,_that.accountId,_that.account,_that.regionOneNumbers,_that.regionTwoNumber,_that.multiplier,_that.drawStatus,_that.drawDate,_that.matchedRegionOneNumbers,_that.matchedRegionTwoNumber,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String accountId, SnAccount? account, List<int> regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List<int>? matchedRegionOneNumbers, int? matchedRegionTwoNumber, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnLotteryTicket():
return $default(_that.id,_that.accountId,_that.account,_that.regionOneNumbers,_that.regionTwoNumber,_that.multiplier,_that.drawStatus,_that.drawDate,_that.matchedRegionOneNumbers,_that.matchedRegionTwoNumber,_that.createdAt,_that.updatedAt,_that.deletedAt);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String accountId, SnAccount? account, List<int> regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List<int>? matchedRegionOneNumbers, int? matchedRegionTwoNumber, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnLotteryTicket() when $default != null:
return $default(_that.id,_that.accountId,_that.account,_that.regionOneNumbers,_that.regionTwoNumber,_that.multiplier,_that.drawStatus,_that.drawDate,_that.matchedRegionOneNumbers,_that.matchedRegionTwoNumber,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnLotteryTicket implements SnLotteryTicket {
const _SnLotteryTicket({required this.id, required this.accountId, required this.account, required final List<int> regionOneNumbers, required this.regionTwoNumber, required this.multiplier, required this.drawStatus, required this.drawDate, required final List<int>? matchedRegionOneNumbers, required this.matchedRegionTwoNumber, required this.createdAt, required this.updatedAt, required this.deletedAt}): _regionOneNumbers = regionOneNumbers,_matchedRegionOneNumbers = matchedRegionOneNumbers;
factory _SnLotteryTicket.fromJson(Map<String, dynamic> json) => _$SnLotteryTicketFromJson(json);
@override final String id;
@override final String accountId;
@override final SnAccount? account;
final List<int> _regionOneNumbers;
@override List<int> get regionOneNumbers {
if (_regionOneNumbers is EqualUnmodifiableListView) return _regionOneNumbers;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_regionOneNumbers);
}
@override final int regionTwoNumber;
@override final int multiplier;
@override final int drawStatus;
@override final DateTime? drawDate;
final List<int>? _matchedRegionOneNumbers;
@override List<int>? get matchedRegionOneNumbers {
final value = _matchedRegionOneNumbers;
if (value == null) return null;
if (_matchedRegionOneNumbers is EqualUnmodifiableListView) return _matchedRegionOneNumbers;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override final int? matchedRegionTwoNumber;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnLotteryTicket
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnLotteryTicketCopyWith<_SnLotteryTicket> get copyWith => __$SnLotteryTicketCopyWithImpl<_SnLotteryTicket>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnLotteryTicketToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnLotteryTicket&&(identical(other.id, id) || other.id == id)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&const DeepCollectionEquality().equals(other._regionOneNumbers, _regionOneNumbers)&&(identical(other.regionTwoNumber, regionTwoNumber) || other.regionTwoNumber == regionTwoNumber)&&(identical(other.multiplier, multiplier) || other.multiplier == multiplier)&&(identical(other.drawStatus, drawStatus) || other.drawStatus == drawStatus)&&(identical(other.drawDate, drawDate) || other.drawDate == drawDate)&&const DeepCollectionEquality().equals(other._matchedRegionOneNumbers, _matchedRegionOneNumbers)&&(identical(other.matchedRegionTwoNumber, matchedRegionTwoNumber) || other.matchedRegionTwoNumber == matchedRegionTwoNumber)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,accountId,account,const DeepCollectionEquality().hash(_regionOneNumbers),regionTwoNumber,multiplier,drawStatus,drawDate,const DeepCollectionEquality().hash(_matchedRegionOneNumbers),matchedRegionTwoNumber,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnLotteryTicket(id: $id, accountId: $accountId, account: $account, regionOneNumbers: $regionOneNumbers, regionTwoNumber: $regionTwoNumber, multiplier: $multiplier, drawStatus: $drawStatus, drawDate: $drawDate, matchedRegionOneNumbers: $matchedRegionOneNumbers, matchedRegionTwoNumber: $matchedRegionTwoNumber, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$SnLotteryTicketCopyWith<$Res> implements $SnLotteryTicketCopyWith<$Res> {
factory _$SnLotteryTicketCopyWith(_SnLotteryTicket value, $Res Function(_SnLotteryTicket) _then) = __$SnLotteryTicketCopyWithImpl;
@override @useResult
$Res call({
String id, String accountId, SnAccount? account, List<int> regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List<int>? matchedRegionOneNumbers, int? matchedRegionTwoNumber, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@override $SnAccountCopyWith<$Res>? get account;
}
/// @nodoc
class __$SnLotteryTicketCopyWithImpl<$Res>
implements _$SnLotteryTicketCopyWith<$Res> {
__$SnLotteryTicketCopyWithImpl(this._self, this._then);
final _SnLotteryTicket _self;
final $Res Function(_SnLotteryTicket) _then;
/// Create a copy of SnLotteryTicket
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? accountId = null,Object? account = freezed,Object? regionOneNumbers = null,Object? regionTwoNumber = null,Object? multiplier = null,Object? drawStatus = null,Object? drawDate = freezed,Object? matchedRegionOneNumbers = freezed,Object? matchedRegionTwoNumber = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnLotteryTicket(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount?,regionOneNumbers: null == regionOneNumbers ? _self._regionOneNumbers : regionOneNumbers // ignore: cast_nullable_to_non_nullable
as List<int>,regionTwoNumber: null == regionTwoNumber ? _self.regionTwoNumber : regionTwoNumber // ignore: cast_nullable_to_non_nullable
as int,multiplier: null == multiplier ? _self.multiplier : multiplier // ignore: cast_nullable_to_non_nullable
as int,drawStatus: null == drawStatus ? _self.drawStatus : drawStatus // ignore: cast_nullable_to_non_nullable
as int,drawDate: freezed == drawDate ? _self.drawDate : drawDate // ignore: cast_nullable_to_non_nullable
as DateTime?,matchedRegionOneNumbers: freezed == matchedRegionOneNumbers ? _self._matchedRegionOneNumbers : matchedRegionOneNumbers // ignore: cast_nullable_to_non_nullable
as List<int>?,matchedRegionTwoNumber: freezed == matchedRegionTwoNumber ? _self.matchedRegionTwoNumber : matchedRegionTwoNumber // ignore: cast_nullable_to_non_nullable
as int?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
/// Create a copy of SnLotteryTicket
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
}
/// @nodoc
mixin _$SnLotteryRecord {
String get id; DateTime get drawDate; List<int> get winningRegionOneNumbers; int get winningRegionTwoNumber; int get totalTickets; int get totalPrizesAwarded; double get totalPrizeAmount; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnLotteryRecord
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnLotteryRecordCopyWith<SnLotteryRecord> get copyWith => _$SnLotteryRecordCopyWithImpl<SnLotteryRecord>(this as SnLotteryRecord, _$identity);
/// Serializes this SnLotteryRecord to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnLotteryRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.drawDate, drawDate) || other.drawDate == drawDate)&&const DeepCollectionEquality().equals(other.winningRegionOneNumbers, winningRegionOneNumbers)&&(identical(other.winningRegionTwoNumber, winningRegionTwoNumber) || other.winningRegionTwoNumber == winningRegionTwoNumber)&&(identical(other.totalTickets, totalTickets) || other.totalTickets == totalTickets)&&(identical(other.totalPrizesAwarded, totalPrizesAwarded) || other.totalPrizesAwarded == totalPrizesAwarded)&&(identical(other.totalPrizeAmount, totalPrizeAmount) || other.totalPrizeAmount == totalPrizeAmount)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,drawDate,const DeepCollectionEquality().hash(winningRegionOneNumbers),winningRegionTwoNumber,totalTickets,totalPrizesAwarded,totalPrizeAmount,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnLotteryRecord(id: $id, drawDate: $drawDate, winningRegionOneNumbers: $winningRegionOneNumbers, winningRegionTwoNumber: $winningRegionTwoNumber, totalTickets: $totalTickets, totalPrizesAwarded: $totalPrizesAwarded, totalPrizeAmount: $totalPrizeAmount, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $SnLotteryRecordCopyWith<$Res> {
factory $SnLotteryRecordCopyWith(SnLotteryRecord value, $Res Function(SnLotteryRecord) _then) = _$SnLotteryRecordCopyWithImpl;
@useResult
$Res call({
String id, DateTime drawDate, List<int> winningRegionOneNumbers, int winningRegionTwoNumber, int totalTickets, int totalPrizesAwarded, double totalPrizeAmount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
}
/// @nodoc
class _$SnLotteryRecordCopyWithImpl<$Res>
implements $SnLotteryRecordCopyWith<$Res> {
_$SnLotteryRecordCopyWithImpl(this._self, this._then);
final SnLotteryRecord _self;
final $Res Function(SnLotteryRecord) _then;
/// Create a copy of SnLotteryRecord
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? drawDate = null,Object? winningRegionOneNumbers = null,Object? winningRegionTwoNumber = null,Object? totalTickets = null,Object? totalPrizesAwarded = null,Object? totalPrizeAmount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,drawDate: null == drawDate ? _self.drawDate : drawDate // ignore: cast_nullable_to_non_nullable
as DateTime,winningRegionOneNumbers: null == winningRegionOneNumbers ? _self.winningRegionOneNumbers : winningRegionOneNumbers // ignore: cast_nullable_to_non_nullable
as List<int>,winningRegionTwoNumber: null == winningRegionTwoNumber ? _self.winningRegionTwoNumber : winningRegionTwoNumber // ignore: cast_nullable_to_non_nullable
as int,totalTickets: null == totalTickets ? _self.totalTickets : totalTickets // ignore: cast_nullable_to_non_nullable
as int,totalPrizesAwarded: null == totalPrizesAwarded ? _self.totalPrizesAwarded : totalPrizesAwarded // ignore: cast_nullable_to_non_nullable
as int,totalPrizeAmount: null == totalPrizeAmount ? _self.totalPrizeAmount : totalPrizeAmount // ignore: cast_nullable_to_non_nullable
as double,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
/// Adds pattern-matching-related methods to [SnLotteryRecord].
extension SnLotteryRecordPatterns on SnLotteryRecord {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnLotteryRecord value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnLotteryRecord() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnLotteryRecord value) $default,){
final _that = this;
switch (_that) {
case _SnLotteryRecord():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnLotteryRecord value)? $default,){
final _that = this;
switch (_that) {
case _SnLotteryRecord() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, DateTime drawDate, List<int> winningRegionOneNumbers, int winningRegionTwoNumber, int totalTickets, int totalPrizesAwarded, double totalPrizeAmount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnLotteryRecord() when $default != null:
return $default(_that.id,_that.drawDate,_that.winningRegionOneNumbers,_that.winningRegionTwoNumber,_that.totalTickets,_that.totalPrizesAwarded,_that.totalPrizeAmount,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, DateTime drawDate, List<int> winningRegionOneNumbers, int winningRegionTwoNumber, int totalTickets, int totalPrizesAwarded, double totalPrizeAmount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnLotteryRecord():
return $default(_that.id,_that.drawDate,_that.winningRegionOneNumbers,_that.winningRegionTwoNumber,_that.totalTickets,_that.totalPrizesAwarded,_that.totalPrizeAmount,_that.createdAt,_that.updatedAt,_that.deletedAt);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, DateTime drawDate, List<int> winningRegionOneNumbers, int winningRegionTwoNumber, int totalTickets, int totalPrizesAwarded, double totalPrizeAmount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnLotteryRecord() when $default != null:
return $default(_that.id,_that.drawDate,_that.winningRegionOneNumbers,_that.winningRegionTwoNumber,_that.totalTickets,_that.totalPrizesAwarded,_that.totalPrizeAmount,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnLotteryRecord implements SnLotteryRecord {
const _SnLotteryRecord({required this.id, required this.drawDate, required final List<int> winningRegionOneNumbers, required this.winningRegionTwoNumber, required this.totalTickets, required this.totalPrizesAwarded, required this.totalPrizeAmount, required this.createdAt, required this.updatedAt, required this.deletedAt}): _winningRegionOneNumbers = winningRegionOneNumbers;
factory _SnLotteryRecord.fromJson(Map<String, dynamic> json) => _$SnLotteryRecordFromJson(json);
@override final String id;
@override final DateTime drawDate;
final List<int> _winningRegionOneNumbers;
@override List<int> get winningRegionOneNumbers {
if (_winningRegionOneNumbers is EqualUnmodifiableListView) return _winningRegionOneNumbers;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_winningRegionOneNumbers);
}
@override final int winningRegionTwoNumber;
@override final int totalTickets;
@override final int totalPrizesAwarded;
@override final double totalPrizeAmount;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnLotteryRecord
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnLotteryRecordCopyWith<_SnLotteryRecord> get copyWith => __$SnLotteryRecordCopyWithImpl<_SnLotteryRecord>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnLotteryRecordToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnLotteryRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.drawDate, drawDate) || other.drawDate == drawDate)&&const DeepCollectionEquality().equals(other._winningRegionOneNumbers, _winningRegionOneNumbers)&&(identical(other.winningRegionTwoNumber, winningRegionTwoNumber) || other.winningRegionTwoNumber == winningRegionTwoNumber)&&(identical(other.totalTickets, totalTickets) || other.totalTickets == totalTickets)&&(identical(other.totalPrizesAwarded, totalPrizesAwarded) || other.totalPrizesAwarded == totalPrizesAwarded)&&(identical(other.totalPrizeAmount, totalPrizeAmount) || other.totalPrizeAmount == totalPrizeAmount)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,drawDate,const DeepCollectionEquality().hash(_winningRegionOneNumbers),winningRegionTwoNumber,totalTickets,totalPrizesAwarded,totalPrizeAmount,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnLotteryRecord(id: $id, drawDate: $drawDate, winningRegionOneNumbers: $winningRegionOneNumbers, winningRegionTwoNumber: $winningRegionTwoNumber, totalTickets: $totalTickets, totalPrizesAwarded: $totalPrizesAwarded, totalPrizeAmount: $totalPrizeAmount, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$SnLotteryRecordCopyWith<$Res> implements $SnLotteryRecordCopyWith<$Res> {
factory _$SnLotteryRecordCopyWith(_SnLotteryRecord value, $Res Function(_SnLotteryRecord) _then) = __$SnLotteryRecordCopyWithImpl;
@override @useResult
$Res call({
String id, DateTime drawDate, List<int> winningRegionOneNumbers, int winningRegionTwoNumber, int totalTickets, int totalPrizesAwarded, double totalPrizeAmount, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
}
/// @nodoc
class __$SnLotteryRecordCopyWithImpl<$Res>
implements _$SnLotteryRecordCopyWith<$Res> {
__$SnLotteryRecordCopyWithImpl(this._self, this._then);
final _SnLotteryRecord _self;
final $Res Function(_SnLotteryRecord) _then;
/// Create a copy of SnLotteryRecord
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? drawDate = null,Object? winningRegionOneNumbers = null,Object? winningRegionTwoNumber = null,Object? totalTickets = null,Object? totalPrizesAwarded = null,Object? totalPrizeAmount = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnLotteryRecord(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,drawDate: null == drawDate ? _self.drawDate : drawDate // ignore: cast_nullable_to_non_nullable
as DateTime,winningRegionOneNumbers: null == winningRegionOneNumbers ? _self._winningRegionOneNumbers : winningRegionOneNumbers // ignore: cast_nullable_to_non_nullable
as List<int>,winningRegionTwoNumber: null == winningRegionTwoNumber ? _self.winningRegionTwoNumber : winningRegionTwoNumber // ignore: cast_nullable_to_non_nullable
as int,totalTickets: null == totalTickets ? _self.totalTickets : totalTickets // ignore: cast_nullable_to_non_nullable
as int,totalPrizesAwarded: null == totalPrizesAwarded ? _self.totalPrizesAwarded : totalPrizesAwarded // ignore: cast_nullable_to_non_nullable
as int,totalPrizeAmount: null == totalPrizeAmount ? _self.totalPrizeAmount : totalPrizeAmount // ignore: cast_nullable_to_non_nullable
as double,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
// dart format on

View File

@@ -414,3 +414,88 @@ Map<String, dynamic> _$SnWalletFundRecipientToJson(
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnLotteryTicket _$SnLotteryTicketFromJson(Map<String, dynamic> json) =>
_SnLotteryTicket(
id: json['id'] as String,
accountId: json['account_id'] as String,
account:
json['account'] == null
? null
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
regionOneNumbers:
(json['region_one_numbers'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
regionTwoNumber: (json['region_two_number'] as num).toInt(),
multiplier: (json['multiplier'] as num).toInt(),
drawStatus: (json['draw_status'] as num).toInt(),
drawDate:
json['draw_date'] == null
? null
: DateTime.parse(json['draw_date'] as String),
matchedRegionOneNumbers:
(json['matched_region_one_numbers'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList(),
matchedRegionTwoNumber:
(json['matched_region_two_number'] as num?)?.toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnLotteryTicketToJson(_SnLotteryTicket instance) =>
<String, dynamic>{
'id': instance.id,
'account_id': instance.accountId,
'account': instance.account?.toJson(),
'region_one_numbers': instance.regionOneNumbers,
'region_two_number': instance.regionTwoNumber,
'multiplier': instance.multiplier,
'draw_status': instance.drawStatus,
'draw_date': instance.drawDate?.toIso8601String(),
'matched_region_one_numbers': instance.matchedRegionOneNumbers,
'matched_region_two_number': instance.matchedRegionTwoNumber,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnLotteryRecord _$SnLotteryRecordFromJson(Map<String, dynamic> json) =>
_SnLotteryRecord(
id: json['id'] as String,
drawDate: DateTime.parse(json['draw_date'] as String),
winningRegionOneNumbers:
(json['winning_region_one_numbers'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
winningRegionTwoNumber:
(json['winning_region_two_number'] as num).toInt(),
totalTickets: (json['total_tickets'] as num).toInt(),
totalPrizesAwarded: (json['total_prizes_awarded'] as num).toInt(),
totalPrizeAmount: (json['total_prize_amount'] as num).toDouble(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnLotteryRecordToJson(_SnLotteryRecord instance) =>
<String, dynamic>{
'id': instance.id,
'draw_date': instance.drawDate.toIso8601String(),
'winning_region_one_numbers': instance.winningRegionOneNumbers,
'winning_region_two_number': instance.winningRegionTwoNumber,
'total_tickets': instance.totalTickets,
'total_prizes_awarded': instance.totalPrizesAwarded,
'total_prize_amount': instance.totalPrizeAmount,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};

810
lib/screens/lottery.dart Normal file
View File

@@ -0,0 +1,810 @@
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 = false;
// 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));
}
// Add region two number
final isSpecialMatched =
ticket.matchedRegionTwoNumber == ticket.regionTwoNumber;
if (isSpecialMatched) hasAnyMatch = true;
numbers.add(
_buildNumberWidget(
context,
ticket.regionTwoNumber,
isMatched: isSpecialMatched,
isSpecial: true,
),
);
final wrapWidget = Wrap(
spacing: 6,
crossAxisAlignment: WrapCrossAlignment.center,
children: numbers,
);
// If no numbers matched and ticket is drawn, apply red background
if (!hasAnyMatch && ticket.drawStatus >= 1) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: wrapWidget,
);
}
return wrapWidget;
}
Widget _buildNumberWidget(
BuildContext context,
int number, {
bool isMatched = false,
bool isSpecial = 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);
}
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(8),
_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 InkWell(
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: 'Multiplier (1-10)',
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 && parsed <= 10) {
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() {
final prizeStructure = {
'5+Special': '1000000.00',
'5': '100000.00',
'4+Special': '5000.00',
'4': '500.00',
'3+Special': '100.00',
'3': '50.00',
'2+Special': '10.00',
'2': '5.00',
'1+Special': '2.00',
'0+Special': '1.00',
};
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.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);
}
}

307
lib/screens/lottery.g.dart Normal file
View File

@@ -0,0 +1,307 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'lottery.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$lotteryTicketsHash() => r'dd17cd721fc3b176ffa0ee0a85d0d850740e5e80';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [lotteryTickets].
@ProviderFor(lotteryTickets)
const lotteryTicketsProvider = LotteryTicketsFamily();
/// See also [lotteryTickets].
class LotteryTicketsFamily extends Family<AsyncValue<List<SnLotteryTicket>>> {
/// See also [lotteryTickets].
const LotteryTicketsFamily();
/// See also [lotteryTickets].
LotteryTicketsProvider call({int offset = 0, int take = 20}) {
return LotteryTicketsProvider(offset: offset, take: take);
}
@override
LotteryTicketsProvider getProviderOverride(
covariant LotteryTicketsProvider provider,
) {
return call(offset: provider.offset, take: provider.take);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'lotteryTicketsProvider';
}
/// See also [lotteryTickets].
class LotteryTicketsProvider
extends AutoDisposeFutureProvider<List<SnLotteryTicket>> {
/// See also [lotteryTickets].
LotteryTicketsProvider({int offset = 0, int take = 20})
: this._internal(
(ref) => lotteryTickets(
ref as LotteryTicketsRef,
offset: offset,
take: take,
),
from: lotteryTicketsProvider,
name: r'lotteryTicketsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$lotteryTicketsHash,
dependencies: LotteryTicketsFamily._dependencies,
allTransitiveDependencies:
LotteryTicketsFamily._allTransitiveDependencies,
offset: offset,
take: take,
);
LotteryTicketsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.offset,
required this.take,
}) : super.internal();
final int offset;
final int take;
@override
Override overrideWith(
FutureOr<List<SnLotteryTicket>> Function(LotteryTicketsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: LotteryTicketsProvider._internal(
(ref) => create(ref as LotteryTicketsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
offset: offset,
take: take,
),
);
}
@override
AutoDisposeFutureProviderElement<List<SnLotteryTicket>> createElement() {
return _LotteryTicketsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is LotteryTicketsProvider &&
other.offset == offset &&
other.take == take;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, offset.hashCode);
hash = _SystemHash.combine(hash, take.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin LotteryTicketsRef on AutoDisposeFutureProviderRef<List<SnLotteryTicket>> {
/// The parameter `offset` of this provider.
int get offset;
/// The parameter `take` of this provider.
int get take;
}
class _LotteryTicketsProviderElement
extends AutoDisposeFutureProviderElement<List<SnLotteryTicket>>
with LotteryTicketsRef {
_LotteryTicketsProviderElement(super.provider);
@override
int get offset => (origin as LotteryTicketsProvider).offset;
@override
int get take => (origin as LotteryTicketsProvider).take;
}
String _$lotteryRecordsHash() => r'55c657460f18d9777741d09013b445ca036863f3';
/// See also [lotteryRecords].
@ProviderFor(lotteryRecords)
const lotteryRecordsProvider = LotteryRecordsFamily();
/// See also [lotteryRecords].
class LotteryRecordsFamily extends Family<AsyncValue<List<SnLotteryRecord>>> {
/// See also [lotteryRecords].
const LotteryRecordsFamily();
/// See also [lotteryRecords].
LotteryRecordsProvider call({int offset = 0, int take = 20}) {
return LotteryRecordsProvider(offset: offset, take: take);
}
@override
LotteryRecordsProvider getProviderOverride(
covariant LotteryRecordsProvider provider,
) {
return call(offset: provider.offset, take: provider.take);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'lotteryRecordsProvider';
}
/// See also [lotteryRecords].
class LotteryRecordsProvider
extends AutoDisposeFutureProvider<List<SnLotteryRecord>> {
/// See also [lotteryRecords].
LotteryRecordsProvider({int offset = 0, int take = 20})
: this._internal(
(ref) => lotteryRecords(
ref as LotteryRecordsRef,
offset: offset,
take: take,
),
from: lotteryRecordsProvider,
name: r'lotteryRecordsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$lotteryRecordsHash,
dependencies: LotteryRecordsFamily._dependencies,
allTransitiveDependencies:
LotteryRecordsFamily._allTransitiveDependencies,
offset: offset,
take: take,
);
LotteryRecordsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.offset,
required this.take,
}) : super.internal();
final int offset;
final int take;
@override
Override overrideWith(
FutureOr<List<SnLotteryRecord>> Function(LotteryRecordsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: LotteryRecordsProvider._internal(
(ref) => create(ref as LotteryRecordsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
offset: offset,
take: take,
),
);
}
@override
AutoDisposeFutureProviderElement<List<SnLotteryRecord>> createElement() {
return _LotteryRecordsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is LotteryRecordsProvider &&
other.offset == offset &&
other.take == take;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, offset.hashCode);
hash = _SystemHash.combine(hash, take.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin LotteryRecordsRef on AutoDisposeFutureProviderRef<List<SnLotteryRecord>> {
/// The parameter `offset` of this provider.
int get offset;
/// The parameter `take` of this provider.
int get take;
}
class _LotteryRecordsProviderElement
extends AutoDisposeFutureProviderElement<List<SnLotteryRecord>>
with LotteryRecordsRef {
_LotteryRecordsProviderElement(super.provider);
@override
int get offset => (origin as LotteryRecordsProvider).offset;
@override
int get take => (origin as LotteryRecordsProvider).take;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart';
import 'package:island/models/wallet.dart';
import 'package:island/pods/network.dart';
import 'package:island/screens/lottery.dart';
import 'package:island/widgets/account/account_pfc.dart';
import 'package:island/widgets/account/account_picker.dart';
import 'package:island/widgets/app_scaffold.dart';
@@ -998,11 +999,6 @@ class _CreateTransferSheetState extends State<CreateTransferSheet> {
}
}
const Map<String, IconData> kCurrencyIconData = {
'points': Symbols.toll,
'golds': Symbols.attach_money,
};
@riverpod
class TransactionListNotifier extends _$TransactionListNotifier
with CursorPagingNotifierMixin<SnTransaction> {
@@ -1249,7 +1245,7 @@ class WalletScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final wallet = ref.watch(walletCurrentProvider);
final tabController = useTabController(initialLength: 2);
final tabController = useTabController(initialLength: 3);
final currentTabIndex = useState(0);
useEffect(() {
@@ -1328,18 +1324,20 @@ class WalletScreen extends HookConsumerWidget {
appBar: AppBar(
title: Text('wallet').tr(),
actions: [
IconButton(
icon: Icon(
currentTabIndex.value == 1
? Symbols.money_bag
: Symbols.swap_horiz,
),
onPressed: currentTabIndex.value == 1 ? createFund : createTransfer,
tooltip:
if (currentTabIndex.value != 2) // Hide for lottery tab
IconButton(
icon: Icon(
currentTabIndex.value == 1
? 'createFund'.tr()
: 'createTransfer'.tr(),
),
? Symbols.money_bag
: Symbols.swap_horiz,
),
onPressed:
currentTabIndex.value == 1 ? createFund : createTransfer,
tooltip:
currentTabIndex.value == 1
? 'createFund'.tr()
: 'createTransfer'.tr(),
),
const Gap(8),
],
),
@@ -1410,6 +1408,7 @@ class WalletScreen extends HookConsumerWidget {
tabs: [
Tab(text: 'transactions'.tr()),
Tab(text: 'myFunds'.tr()),
Tab(text: 'lottery'.tr()),
],
),
),
@@ -1487,6 +1486,9 @@ class WalletScreen extends HookConsumerWidget {
// My Funds Tab
_buildFundsList(context, ref),
// Lottery Tab
const LotteryTab(),
],
),
);
@@ -1859,3 +1861,8 @@ class WalletScreen extends HookConsumerWidget {
}
}
}
const Map<String, IconData> kCurrencyIconData = {
'points': Symbols.toll,
'golds': Symbols.attach_money,
};