From cdf1413fe07a7714e80935d504f26b9269f7642e Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 25 Oct 2025 00:18:08 +0800 Subject: [PATCH] :sparkles: Lotteries --- assets/i18n/en-US.json | 32 +- lib/models/wallet.dart | 41 ++ lib/models/wallet.freezed.dart | 621 +++++++++++++++++++++++++ lib/models/wallet.g.dart | 85 ++++ lib/screens/lottery.dart | 810 +++++++++++++++++++++++++++++++++ lib/screens/lottery.g.dart | 307 +++++++++++++ lib/screens/wallet.dart | 41 +- 7 files changed, 1919 insertions(+), 18 deletions(-) create mode 100644 lib/screens/lottery.dart create mode 100644 lib/screens/lottery.g.dart diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 65a25b75..f3ab563d 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -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" } diff --git a/lib/models/wallet.dart b/lib/models/wallet.dart index fae33fed..818fd10f 100644 --- a/lib/models/wallet.dart +++ b/lib/models/wallet.dart @@ -211,3 +211,44 @@ sealed class SnWalletFundRecipient with _$SnWalletFundRecipient { factory SnWalletFundRecipient.fromJson(Map json) => _$SnWalletFundRecipientFromJson(json); } + +@freezed +sealed class SnLotteryTicket with _$SnLotteryTicket { + const factory SnLotteryTicket({ + required String id, + required String accountId, + required SnAccount? account, + required List regionOneNumbers, + required int regionTwoNumber, + required int multiplier, + required int drawStatus, + required DateTime? drawDate, + required List? matchedRegionOneNumbers, + required int? matchedRegionTwoNumber, + required DateTime createdAt, + required DateTime updatedAt, + required DateTime? deletedAt, + }) = _SnLotteryTicket; + + factory SnLotteryTicket.fromJson(Map json) => + _$SnLotteryTicketFromJson(json); +} + +@freezed +sealed class SnLotteryRecord with _$SnLotteryRecord { + const factory SnLotteryRecord({ + required String id, + required DateTime drawDate, + required List 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 json) => + _$SnLotteryRecordFromJson(json); +} diff --git a/lib/models/wallet.freezed.dart b/lib/models/wallet.freezed.dart index 73c34303..cf74838d 100644 --- a/lib/models/wallet.freezed.dart +++ b/lib/models/wallet.freezed.dart @@ -3184,4 +3184,625 @@ $SnAccountCopyWith<$Res>? get recipientAccount { } } + +/// @nodoc +mixin _$SnLotteryTicket { + + String get id; String get accountId; SnAccount? get account; List get regionOneNumbers; int get regionTwoNumber; int get multiplier; int get drawStatus; DateTime? get drawDate; List? 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 get copyWith => _$SnLotteryTicketCopyWithImpl(this as SnLotteryTicket, _$identity); + + /// Serializes this SnLotteryTicket to a JSON map. + Map 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 regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List? 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,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?,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 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 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? 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 Function( String id, String accountId, SnAccount? account, List regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List? 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 Function( String id, String accountId, SnAccount? account, List regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List? 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? Function( String id, String accountId, SnAccount? account, List regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List? 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 regionOneNumbers, required this.regionTwoNumber, required this.multiplier, required this.drawStatus, required this.drawDate, required final List? matchedRegionOneNumbers, required this.matchedRegionTwoNumber, required this.createdAt, required this.updatedAt, required this.deletedAt}): _regionOneNumbers = regionOneNumbers,_matchedRegionOneNumbers = matchedRegionOneNumbers; + factory _SnLotteryTicket.fromJson(Map json) => _$SnLotteryTicketFromJson(json); + +@override final String id; +@override final String accountId; +@override final SnAccount? account; + final List _regionOneNumbers; +@override List 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? _matchedRegionOneNumbers; +@override List? 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 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 regionOneNumbers, int regionTwoNumber, int multiplier, int drawStatus, DateTime? drawDate, List? 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,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?,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 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 get copyWith => _$SnLotteryRecordCopyWithImpl(this as SnLotteryRecord, _$identity); + + /// Serializes this SnLotteryRecord to a JSON map. + Map 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 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,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 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 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? 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 Function( String id, DateTime drawDate, List 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 Function( String id, DateTime drawDate, List 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? Function( String id, DateTime drawDate, List 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 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 json) => _$SnLotteryRecordFromJson(json); + +@override final String id; +@override final DateTime drawDate; + final List _winningRegionOneNumbers; +@override List 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 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 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,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 diff --git a/lib/models/wallet.g.dart b/lib/models/wallet.g.dart index 6e7e73d4..61bf94a7 100644 --- a/lib/models/wallet.g.dart +++ b/lib/models/wallet.g.dart @@ -414,3 +414,88 @@ Map _$SnWalletFundRecipientToJson( 'updated_at': instance.updatedAt.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(), }; + +_SnLotteryTicket _$SnLotteryTicketFromJson(Map json) => + _SnLotteryTicket( + id: json['id'] as String, + accountId: json['account_id'] as String, + account: + json['account'] == null + ? null + : SnAccount.fromJson(json['account'] as Map), + regionOneNumbers: + (json['region_one_numbers'] as List) + .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?) + ?.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 _$SnLotteryTicketToJson(_SnLotteryTicket instance) => + { + '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 json) => + _SnLotteryRecord( + id: json['id'] as String, + drawDate: DateTime.parse(json['draw_date'] as String), + winningRegionOneNumbers: + (json['winning_region_one_numbers'] as List) + .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 _$SnLotteryRecordToJson(_SnLotteryRecord instance) => + { + '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(), + }; diff --git a/lib/screens/lottery.dart b/lib/screens/lottery.dart new file mode 100644 index 00000000..c8d76ef3 --- /dev/null +++ b/lib/screens/lottery.dart @@ -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> 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> 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 _showLotteryPurchaseSheet( + BuildContext context, + WidgetRef ref, + ) async { + final result = await showModalBottomSheet>( + context: context, + useRootNavigator: true, + isScrollControlled: true, + builder: (context) => const LotteryPurchaseSheet(), + ); + + if (result != null && context.mounted) { + await _handleLotteryPurchase(context, ref, result); + } + } + + Future _handleLotteryPurchase( + BuildContext context, + WidgetRef ref, + Map 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 = []; + + // 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 createState() => _LotteryPurchaseSheetState(); +} + +class _LotteryPurchaseSheetState extends State { + final List 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 _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); + } +} diff --git a/lib/screens/lottery.g.dart b/lib/screens/lottery.g.dart new file mode 100644 index 00000000..a06ef056 --- /dev/null +++ b/lib/screens/lottery.g.dart @@ -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>> { + /// 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? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'lotteryTicketsProvider'; +} + +/// See also [lotteryTickets]. +class LotteryTicketsProvider + extends AutoDisposeFutureProvider> { + /// 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> 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> 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> { + /// The parameter `offset` of this provider. + int get offset; + + /// The parameter `take` of this provider. + int get take; +} + +class _LotteryTicketsProviderElement + extends AutoDisposeFutureProviderElement> + 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>> { + /// 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? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'lotteryRecordsProvider'; +} + +/// See also [lotteryRecords]. +class LotteryRecordsProvider + extends AutoDisposeFutureProvider> { + /// 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> 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> 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> { + /// The parameter `offset` of this provider. + int get offset; + + /// The parameter `take` of this provider. + int get take; +} + +class _LotteryRecordsProviderElement + extends AutoDisposeFutureProviderElement> + 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 diff --git a/lib/screens/wallet.dart b/lib/screens/wallet.dart index 2eafd7f7..40c9f7c7 100644 --- a/lib/screens/wallet.dart +++ b/lib/screens/wallet.dart @@ -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 { } } -const Map kCurrencyIconData = { - 'points': Symbols.toll, - 'golds': Symbols.attach_money, -}; - @riverpod class TransactionListNotifier extends _$TransactionListNotifier with CursorPagingNotifierMixin { @@ -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 kCurrencyIconData = { + 'points': Symbols.toll, + 'golds': Symbols.attach_money, +};