🎨 Use feature based folder structure

This commit is contained in:
2026-02-06 00:37:02 +08:00
parent 62a3ea26e3
commit 862e3b451b
539 changed files with 8406 additions and 5056 deletions

1817
lib/wallet/wallet.dart Normal file

File diff suppressed because it is too large Load Diff

162
lib/wallet/wallet.g.dart Normal file
View File

@@ -0,0 +1,162 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'wallet.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(walletCurrent)
final walletCurrentProvider = WalletCurrentProvider._();
final class WalletCurrentProvider
extends
$FunctionalProvider<
AsyncValue<SnWallet?>,
SnWallet?,
FutureOr<SnWallet?>
>
with $FutureModifier<SnWallet?>, $FutureProvider<SnWallet?> {
WalletCurrentProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'walletCurrentProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$walletCurrentHash();
@$internal
@override
$FutureProviderElement<SnWallet?> $createElement($ProviderPointer pointer) =>
$FutureProviderElement(pointer);
@override
FutureOr<SnWallet?> create(Ref ref) {
return walletCurrent(ref);
}
}
String _$walletCurrentHash() => r'6a654d34aa7e3002edf97749e386b0bd3db641bb';
@ProviderFor(walletStats)
final walletStatsProvider = WalletStatsProvider._();
final class WalletStatsProvider
extends
$FunctionalProvider<
AsyncValue<SnWalletStats>,
SnWalletStats,
FutureOr<SnWalletStats>
>
with $FutureModifier<SnWalletStats>, $FutureProvider<SnWalletStats> {
WalletStatsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'walletStatsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$walletStatsHash();
@$internal
@override
$FutureProviderElement<SnWalletStats> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<SnWalletStats> create(Ref ref) {
return walletStats(ref);
}
}
String _$walletStatsHash() => r'fa265e0fa6e59e1687f6e00c2f9579aa619fcefd';
@ProviderFor(walletFund)
final walletFundProvider = WalletFundFamily._();
final class WalletFundProvider
extends
$FunctionalProvider<
AsyncValue<SnWalletFund>,
SnWalletFund,
FutureOr<SnWalletFund>
>
with $FutureModifier<SnWalletFund>, $FutureProvider<SnWalletFund> {
WalletFundProvider._({
required WalletFundFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'walletFundProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$walletFundHash();
@override
String toString() {
return r'walletFundProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<SnWalletFund> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<SnWalletFund> create(Ref ref) {
final argument = this.argument as String;
return walletFund(ref, argument);
}
@override
bool operator ==(Object other) {
return other is WalletFundProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$walletFundHash() => r'adbbc98e0054ee3687c93b7905dabaa84bfeaf1f';
final class WalletFundFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<SnWalletFund>, String> {
WalletFundFamily._()
: super(
retry: null,
name: r'walletFundProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
WalletFundProvider call(String fundId) =>
WalletFundProvider._(argument: fundId, from: this);
@override
String toString() => r'walletFundProvider';
}

View File

@@ -0,0 +1,257 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/accounts/accounts_models/account.dart';
part 'wallet.freezed.dart';
part 'wallet.g.dart';
@freezed
sealed class SnWallet with _$SnWallet {
const factory SnWallet({
required String id,
required List<SnWalletPocket> pockets,
required String accountId,
required SnAccount? account,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnWallet;
factory SnWallet.fromJson(Map<String, dynamic> json) =>
_$SnWalletFromJson(json);
}
@freezed
sealed class SnWalletStats with _$SnWalletStats {
const factory SnWalletStats({
required DateTime periodBegin,
required DateTime periodEnd,
required int totalTransactions,
required int totalOrders,
required double totalIncome,
required double totalOutgoing,
required double sum,
@Default({}) Map<String, double> incomeCategories,
@Default({}) Map<String, double> outgoingCategories,
}) = _SnWalletStats;
factory SnWalletStats.fromJson(Map<String, dynamic> json) =>
_$SnWalletStatsFromJson(json);
}
@freezed
sealed class SnWalletPocket with _$SnWalletPocket {
const factory SnWalletPocket({
required String id,
required String currency,
required double amount,
required String walletId,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnWalletPocket;
factory SnWalletPocket.fromJson(Map<String, dynamic> json) =>
_$SnWalletPocketFromJson(json);
}
@freezed
sealed class SnTransaction with _$SnTransaction {
const factory SnTransaction({
required String id,
required String currency,
required double amount,
required String? remarks,
required int type,
required String? payerWalletId,
required SnWallet? payerWallet,
required String? payeeWalletId,
required SnWallet? payeeWallet,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnTransaction;
factory SnTransaction.fromJson(Map<String, dynamic> json) =>
_$SnTransactionFromJson(json);
}
@freezed
sealed class SnWalletSubscription with _$SnWalletSubscription {
const factory SnWalletSubscription({
required String id,
required DateTime begunAt,
required DateTime? endedAt,
required String identifier,
@Default(true) bool isActive,
@Default(false) bool isFreeTrial,
@Default(1) int status,
required String? paymentMethod,
required Map<String, dynamic>? paymentDetails,
required double? basePrice,
required String? couponId,
required dynamic coupon,
required DateTime? renewalAt,
required String accountId,
required SnAccount? account,
@Default(true) bool isAvailable,
required double? finalPrice,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnWalletSubscription;
factory SnWalletSubscription.fromJson(Map<String, dynamic> json) =>
_$SnWalletSubscriptionFromJson(json);
}
@freezed
sealed class SnWalletSubscriptionRef with _$SnWalletSubscriptionRef {
const factory SnWalletSubscriptionRef({
required String id,
required bool isActive,
required String accountId,
required DateTime createdAt,
required DateTime? deletedAt,
required DateTime updatedAt,
required String identifier,
}) = _SnWalletSubscriptionRef;
factory SnWalletSubscriptionRef.fromJson(Map<String, dynamic> json) =>
_$SnWalletSubscriptionRefFromJson(json);
}
@freezed
sealed class SnWalletOrder with _$SnWalletOrder {
const factory SnWalletOrder({
required String id,
required int status,
required String currency,
required String? remarks,
required String appIdentifier,
@Default({}) Map<String, dynamic> meta,
required int amount,
required DateTime expiredAt,
required String? payeeWalletId,
required String? transactionId,
required String? issuerAppId,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnWalletOrder;
factory SnWalletOrder.fromJson(Map<String, dynamic> json) =>
_$SnWalletOrderFromJson(json);
}
@freezed
sealed class SnWalletGift with _$SnWalletGift {
const factory SnWalletGift({
required String id,
required String giftCode,
required String subscriptionIdentifier,
required String? recipientId,
required SnAccount? recipient,
required String gifterId,
required SnAccount? gifter,
required String? redeemerId,
required SnAccount? redeemer,
required String? message,
required int status,
required DateTime? redeemedAt,
required DateTime? expiredAt,
required String? subscriptionId,
required SnWalletSubscription? subscription,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnWalletGift;
factory SnWalletGift.fromJson(Map<String, dynamic> json) =>
_$SnWalletGiftFromJson(json);
}
@freezed
sealed class SnWalletFund with _$SnWalletFund {
const factory SnWalletFund({
required String id,
required String currency,
required double totalAmount,
required double remainingAmount,
required int amountOfSplits,
required int splitType, // 0: even, 1: random
required int
status, // 0: created, 1: partially claimed, 2: fully claimed, 3: expired
required String? message,
required String creatorAccountId,
required SnAccount? creatorAccount,
required DateTime expiredAt,
required List<SnWalletFundRecipient> recipients,
required bool isOpen,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnWalletFund;
factory SnWalletFund.fromJson(Map<String, dynamic> json) =>
_$SnWalletFundFromJson(json);
}
@freezed
sealed class SnWalletFundRecipient with _$SnWalletFundRecipient {
const factory SnWalletFundRecipient({
required String id,
required String fundId,
required String recipientAccountId,
required SnAccount? recipientAccount,
required double amount,
required bool isReceived,
required DateTime? receivedAt,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _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);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,472 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'wallet.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_SnWallet _$SnWalletFromJson(Map<String, dynamic> json) => _SnWallet(
id: json['id'] as String,
pockets: (json['pockets'] as List<dynamic>)
.map((e) => SnWalletPocket.fromJson(e as Map<String, dynamic>))
.toList(),
accountId: json['account_id'] as String,
account: json['account'] == null
? null
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
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> _$SnWalletToJson(_SnWallet instance) => <String, dynamic>{
'id': instance.id,
'pockets': instance.pockets.map((e) => e.toJson()).toList(),
'account_id': instance.accountId,
'account': instance.account?.toJson(),
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnWalletStats _$SnWalletStatsFromJson(Map<String, dynamic> json) =>
_SnWalletStats(
periodBegin: DateTime.parse(json['period_begin'] as String),
periodEnd: DateTime.parse(json['period_end'] as String),
totalTransactions: (json['total_transactions'] as num).toInt(),
totalOrders: (json['total_orders'] as num).toInt(),
totalIncome: (json['total_income'] as num).toDouble(),
totalOutgoing: (json['total_outgoing'] as num).toDouble(),
sum: (json['sum'] as num).toDouble(),
incomeCategories:
(json['income_categories'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, (e as num).toDouble()),
) ??
const {},
outgoingCategories:
(json['outgoing_categories'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, (e as num).toDouble()),
) ??
const {},
);
Map<String, dynamic> _$SnWalletStatsToJson(_SnWalletStats instance) =>
<String, dynamic>{
'period_begin': instance.periodBegin.toIso8601String(),
'period_end': instance.periodEnd.toIso8601String(),
'total_transactions': instance.totalTransactions,
'total_orders': instance.totalOrders,
'total_income': instance.totalIncome,
'total_outgoing': instance.totalOutgoing,
'sum': instance.sum,
'income_categories': instance.incomeCategories,
'outgoing_categories': instance.outgoingCategories,
};
_SnWalletPocket _$SnWalletPocketFromJson(Map<String, dynamic> json) =>
_SnWalletPocket(
id: json['id'] as String,
currency: json['currency'] as String,
amount: (json['amount'] as num).toDouble(),
walletId: json['wallet_id'] as String,
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> _$SnWalletPocketToJson(_SnWalletPocket instance) =>
<String, dynamic>{
'id': instance.id,
'currency': instance.currency,
'amount': instance.amount,
'wallet_id': instance.walletId,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnTransaction _$SnTransactionFromJson(Map<String, dynamic> json) =>
_SnTransaction(
id: json['id'] as String,
currency: json['currency'] as String,
amount: (json['amount'] as num).toDouble(),
remarks: json['remarks'] as String?,
type: (json['type'] as num).toInt(),
payerWalletId: json['payer_wallet_id'] as String?,
payerWallet: json['payer_wallet'] == null
? null
: SnWallet.fromJson(json['payer_wallet'] as Map<String, dynamic>),
payeeWalletId: json['payee_wallet_id'] as String?,
payeeWallet: json['payee_wallet'] == null
? null
: SnWallet.fromJson(json['payee_wallet'] as Map<String, dynamic>),
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> _$SnTransactionToJson(_SnTransaction instance) =>
<String, dynamic>{
'id': instance.id,
'currency': instance.currency,
'amount': instance.amount,
'remarks': instance.remarks,
'type': instance.type,
'payer_wallet_id': instance.payerWalletId,
'payer_wallet': instance.payerWallet?.toJson(),
'payee_wallet_id': instance.payeeWalletId,
'payee_wallet': instance.payeeWallet?.toJson(),
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnWalletSubscription _$SnWalletSubscriptionFromJson(
Map<String, dynamic> json,
) => _SnWalletSubscription(
id: json['id'] as String,
begunAt: DateTime.parse(json['begun_at'] as String),
endedAt: json['ended_at'] == null
? null
: DateTime.parse(json['ended_at'] as String),
identifier: json['identifier'] as String,
isActive: json['is_active'] as bool? ?? true,
isFreeTrial: json['is_free_trial'] as bool? ?? false,
status: (json['status'] as num?)?.toInt() ?? 1,
paymentMethod: json['payment_method'] as String?,
paymentDetails: json['payment_details'] as Map<String, dynamic>?,
basePrice: (json['base_price'] as num?)?.toDouble(),
couponId: json['coupon_id'] as String?,
coupon: json['coupon'],
renewalAt: json['renewal_at'] == null
? null
: DateTime.parse(json['renewal_at'] as String),
accountId: json['account_id'] as String,
account: json['account'] == null
? null
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
isAvailable: json['is_available'] as bool? ?? true,
finalPrice: (json['final_price'] 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> _$SnWalletSubscriptionToJson(
_SnWalletSubscription instance,
) => <String, dynamic>{
'id': instance.id,
'begun_at': instance.begunAt.toIso8601String(),
'ended_at': instance.endedAt?.toIso8601String(),
'identifier': instance.identifier,
'is_active': instance.isActive,
'is_free_trial': instance.isFreeTrial,
'status': instance.status,
'payment_method': instance.paymentMethod,
'payment_details': instance.paymentDetails,
'base_price': instance.basePrice,
'coupon_id': instance.couponId,
'coupon': instance.coupon,
'renewal_at': instance.renewalAt?.toIso8601String(),
'account_id': instance.accountId,
'account': instance.account?.toJson(),
'is_available': instance.isAvailable,
'final_price': instance.finalPrice,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnWalletSubscriptionRef _$SnWalletSubscriptionRefFromJson(
Map<String, dynamic> json,
) => _SnWalletSubscriptionRef(
id: json['id'] as String,
isActive: json['is_active'] as bool,
accountId: json['account_id'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
identifier: json['identifier'] as String,
);
Map<String, dynamic> _$SnWalletSubscriptionRefToJson(
_SnWalletSubscriptionRef instance,
) => <String, dynamic>{
'id': instance.id,
'is_active': instance.isActive,
'account_id': instance.accountId,
'created_at': instance.createdAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'identifier': instance.identifier,
};
_SnWalletOrder _$SnWalletOrderFromJson(Map<String, dynamic> json) =>
_SnWalletOrder(
id: json['id'] as String,
status: (json['status'] as num).toInt(),
currency: json['currency'] as String,
remarks: json['remarks'] as String?,
appIdentifier: json['app_identifier'] as String,
meta: json['meta'] as Map<String, dynamic>? ?? const {},
amount: (json['amount'] as num).toInt(),
expiredAt: DateTime.parse(json['expired_at'] as String),
payeeWalletId: json['payee_wallet_id'] as String?,
transactionId: json['transaction_id'] as String?,
issuerAppId: json['issuer_app_id'] as String?,
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> _$SnWalletOrderToJson(_SnWalletOrder instance) =>
<String, dynamic>{
'id': instance.id,
'status': instance.status,
'currency': instance.currency,
'remarks': instance.remarks,
'app_identifier': instance.appIdentifier,
'meta': instance.meta,
'amount': instance.amount,
'expired_at': instance.expiredAt.toIso8601String(),
'payee_wallet_id': instance.payeeWalletId,
'transaction_id': instance.transactionId,
'issuer_app_id': instance.issuerAppId,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnWalletGift _$SnWalletGiftFromJson(Map<String, dynamic> json) =>
_SnWalletGift(
id: json['id'] as String,
giftCode: json['gift_code'] as String,
subscriptionIdentifier: json['subscription_identifier'] as String,
recipientId: json['recipient_id'] as String?,
recipient: json['recipient'] == null
? null
: SnAccount.fromJson(json['recipient'] as Map<String, dynamic>),
gifterId: json['gifter_id'] as String,
gifter: json['gifter'] == null
? null
: SnAccount.fromJson(json['gifter'] as Map<String, dynamic>),
redeemerId: json['redeemer_id'] as String?,
redeemer: json['redeemer'] == null
? null
: SnAccount.fromJson(json['redeemer'] as Map<String, dynamic>),
message: json['message'] as String?,
status: (json['status'] as num).toInt(),
redeemedAt: json['redeemed_at'] == null
? null
: DateTime.parse(json['redeemed_at'] as String),
expiredAt: json['expired_at'] == null
? null
: DateTime.parse(json['expired_at'] as String),
subscriptionId: json['subscription_id'] as String?,
subscription: json['subscription'] == null
? null
: SnWalletSubscription.fromJson(
json['subscription'] as Map<String, dynamic>,
),
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> _$SnWalletGiftToJson(_SnWalletGift instance) =>
<String, dynamic>{
'id': instance.id,
'gift_code': instance.giftCode,
'subscription_identifier': instance.subscriptionIdentifier,
'recipient_id': instance.recipientId,
'recipient': instance.recipient?.toJson(),
'gifter_id': instance.gifterId,
'gifter': instance.gifter?.toJson(),
'redeemer_id': instance.redeemerId,
'redeemer': instance.redeemer?.toJson(),
'message': instance.message,
'status': instance.status,
'redeemed_at': instance.redeemedAt?.toIso8601String(),
'expired_at': instance.expiredAt?.toIso8601String(),
'subscription_id': instance.subscriptionId,
'subscription': instance.subscription?.toJson(),
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnWalletFund _$SnWalletFundFromJson(Map<String, dynamic> json) =>
_SnWalletFund(
id: json['id'] as String,
currency: json['currency'] as String,
totalAmount: (json['total_amount'] as num).toDouble(),
remainingAmount: (json['remaining_amount'] as num).toDouble(),
amountOfSplits: (json['amount_of_splits'] as num).toInt(),
splitType: (json['split_type'] as num).toInt(),
status: (json['status'] as num).toInt(),
message: json['message'] as String?,
creatorAccountId: json['creator_account_id'] as String,
creatorAccount: json['creator_account'] == null
? null
: SnAccount.fromJson(json['creator_account'] as Map<String, dynamic>),
expiredAt: DateTime.parse(json['expired_at'] as String),
recipients: (json['recipients'] as List<dynamic>)
.map((e) => SnWalletFundRecipient.fromJson(e as Map<String, dynamic>))
.toList(),
isOpen: json['is_open'] as bool,
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> _$SnWalletFundToJson(_SnWalletFund instance) =>
<String, dynamic>{
'id': instance.id,
'currency': instance.currency,
'total_amount': instance.totalAmount,
'remaining_amount': instance.remainingAmount,
'amount_of_splits': instance.amountOfSplits,
'split_type': instance.splitType,
'status': instance.status,
'message': instance.message,
'creator_account_id': instance.creatorAccountId,
'creator_account': instance.creatorAccount?.toJson(),
'expired_at': instance.expiredAt.toIso8601String(),
'recipients': instance.recipients.map((e) => e.toJson()).toList(),
'is_open': instance.isOpen,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnWalletFundRecipient _$SnWalletFundRecipientFromJson(
Map<String, dynamic> json,
) => _SnWalletFundRecipient(
id: json['id'] as String,
fundId: json['fund_id'] as String,
recipientAccountId: json['recipient_account_id'] as String,
recipientAccount: json['recipient_account'] == null
? null
: SnAccount.fromJson(json['recipient_account'] as Map<String, dynamic>),
amount: (json['amount'] as num).toDouble(),
isReceived: json['is_received'] as bool,
receivedAt: json['received_at'] == null
? null
: DateTime.parse(json['received_at'] as String),
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> _$SnWalletFundRecipientToJson(
_SnWalletFundRecipient instance,
) => <String, dynamic>{
'id': instance.id,
'fund_id': instance.fundId,
'recipient_account_id': instance.recipientAccountId,
'recipient_account': instance.recipientAccount?.toJson(),
'amount': instance.amount,
'is_received': instance.isReceived,
'received_at': instance.receivedAt?.toIso8601String(),
'created_at': instance.createdAt.toIso8601String(),
'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(),
};

View File

@@ -0,0 +1,598 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/wallet/wallet_models/wallet.dart';
import 'package:island/core/network.dart';
import 'package:island/accounts/accounts_pod.dart';
import 'package:island/shared/widgets/alert.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:easy_localization/easy_localization.dart';
part 'fund_envelope.g.dart';
@riverpod
Future<SnWalletFund> walletFund(Ref ref, String fundId) async {
final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get('/wallet/wallets/funds/$fundId');
return SnWalletFund.fromJson(resp.data);
}
class FundEnvelopeWidget extends HookConsumerWidget {
const FundEnvelopeWidget({
super.key,
required this.fundId,
this.maxWidth,
this.margin,
});
final String fundId;
final double? maxWidth;
final EdgeInsetsGeometry? margin;
@override
Widget build(BuildContext context, WidgetRef ref) {
final fundAsync = ref.watch(walletFundProvider(fundId));
return Container(
width: maxWidth,
margin: margin ?? const EdgeInsets.symmetric(vertical: 8),
child: fundAsync.when(
loading: () => Card(
margin: EdgeInsets.zero,
child: const Padding(
padding: EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator()),
),
),
error: (error, stack) => Card(
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Icon(
Icons.error_outline,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(height: 8),
Text(
'fundEnvelopeLoadFailed'.tr(),
style: TextStyle(color: Theme.of(context).colorScheme.error),
),
],
),
),
),
data: (fund) => Card(
margin: EdgeInsets.zero,
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () => _showClaimDialog(context, ref, fund),
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Fund title and status
Row(
children: [
Icon(
Icons.account_balance_wallet,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'fundEnvelope'.tr(),
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.w600),
),
),
_buildStatusChips(context, fund),
],
),
const SizedBox(height: 12),
// Amount information
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}',
style: Theme.of(context).textTheme.headlineSmall
?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 4),
if (fund.remainingAmount != fund.totalAmount)
Text(
'fundEnvelopeRemaining'.tr(
args: [
fund.remainingAmount.toStringAsFixed(2),
fund.currency,
],
),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: Theme.of(
context,
).colorScheme.secondary,
fontWeight: FontWeight.w500,
),
),
Text(
'fundEnvelopeSplit'.tr(
args: [
fund.splitType == 0
? 'fundEnvelopeSplitEvenly'.tr()
: 'fundEnvelopeSplitRandomly'.tr(),
],
),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: Theme.of(context)
.textTheme
.bodySmall
?.color
?.withOpacity(0.7),
),
),
],
),
],
),
// Recipients overview
if (fund.recipients.isNotEmpty) ...[
const SizedBox(height: 12),
_buildRecipientsOverview(context, fund),
],
// Message
if (fund.message != null && fund.message!.isNotEmpty) ...[
const SizedBox(height: 12),
Text(
'"${fund.message}"',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontStyle: FontStyle.italic,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
// Creator info
if (fund.creatorAccount != null) ...[
const SizedBox(height: 12),
Row(
children: [
Icon(
Icons.person,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(width: 4),
Text(
fund.creatorAccount!.nick,
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
),
],
),
],
// Expiry info
const SizedBox(height: 6),
Row(
children: [
Icon(
Icons.schedule,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(width: 4),
Text(
_formatDate(fund.expiredAt),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
],
),
),
),
),
),
);
}
void _showClaimDialog(
BuildContext context,
WidgetRef ref,
SnWalletFund fund,
) {
showDialog(
context: context,
builder: (dialogContext) => FundClaimDialog(
fund: fund,
onClaim: () async {
try {
final apiClient = ref.read(apiClientProvider);
await apiClient.post('/wallet/wallets/funds/${fund.id}/receive');
// Refresh the fund data after claiming
ref.invalidate(walletFundProvider(fund.id));
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop();
showSnackBar('fundEnvelopeClaimSuccess'.tr());
}
} catch (e) {
showErrorAlert(e);
}
},
),
);
}
Widget _buildStatusChip(BuildContext context, int status) {
String text;
Color color;
switch (status) {
case 0:
text = 'fundEnvelopeStatusCreated'.tr();
color = Colors.blue;
break;
case 1:
text = 'fundEnvelopeStatusPartial'.tr();
color = Colors.orange;
break;
case 2:
text = 'fundEnvelopeStatusCompleted'.tr();
color = Colors.green;
break;
case 3:
text = 'fundEnvelopeStatusExpired'.tr();
color = Colors.red;
break;
default:
text = 'fundEnvelopeStatusUnknown'.tr();
color = Colors.grey;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Text(
text,
style: TextStyle(
fontSize: 10,
color: color,
fontWeight: FontWeight.w500,
),
),
);
}
Widget _buildStatusChips(BuildContext context, SnWalletFund fund) {
return Row(
children: [
if (fund.isOpen) ...[
_buildOpenFundBadge(context),
const SizedBox(width: 6),
],
_buildStatusChip(context, fund.status),
],
);
}
Widget _buildOpenFundBadge(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.green.withOpacity(0.3)),
),
child: Text(
'Open Fund'.tr(),
style: const TextStyle(
fontSize: 10,
color: Colors.green,
fontWeight: FontWeight.w500,
),
),
);
}
Widget _buildRecipientsOverview(BuildContext context, SnWalletFund fund) {
final claimedCount = fund.recipients.where((r) => r.isReceived).length;
final totalCount = fund.isOpen
? fund.amountOfSplits
: fund.recipients.length;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'fundEnvelopeRecipients'.tr(
args: [claimedCount.toString(), totalCount.toString()],
),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 4),
LinearProgressIndicator(
value: totalCount > 0 ? claimedCount / totalCount : 0,
backgroundColor: Theme.of(
context,
).colorScheme.surfaceContainerHighest,
),
],
);
}
String _formatDate(DateTime date) {
try {
final now = DateTime.now();
final difference = date.difference(now);
if (difference.isNegative) {
final days = difference.inDays.abs();
return 'fundEnvelopeExpiredDaysAgo'.plural(
days,
args: [days.toString()],
);
} else if (difference.inDays == 0) {
final hours = difference.inHours;
if (hours == 0) {
return 'fundEnvelopeExpiresSoon'.tr();
}
return 'fundEnvelopeExpiresInHours'.plural(
hours,
args: [hours.toString()],
);
} else if (difference.inDays < 7) {
final days = difference.inDays;
return 'fundEnvelopeExpiresInDays'.plural(
days,
args: [days.toString()],
);
} else {
return '${date.day}/${date.month}/${date.year}';
}
} catch (e) {
return date.toString();
}
}
}
class FundClaimDialog extends HookConsumerWidget {
const FundClaimDialog({super.key, required this.fund, required this.onClaim});
final SnWalletFund fund;
final VoidCallback onClaim;
@override
Widget build(BuildContext context, WidgetRef ref) {
final userInfo = ref.watch(userInfoProvider);
// Check if user can claim
final now = DateTime.now();
final isExpired = fund.expiredAt.isBefore(now);
final hasRemainingAmount = fund.remainingAmount > 0;
final hasUserClaimed =
userInfo.value != null &&
fund.recipients.any(
(recipient) =>
recipient.recipientAccountId == userInfo.value!.id &&
recipient.isReceived,
);
final userAbleToClaim =
userInfo.value != null &&
(fund.isOpen ||
fund.recipients.any(
(recipient) => recipient.recipientAccountId == userInfo.value!.id,
));
final canClaim =
!isExpired && hasRemainingAmount && !hasUserClaimed && userAbleToClaim;
// Get claimed recipients for display
final claimedRecipients = fund.recipients
.where((r) => r.isReceived)
.toList();
final unclaimedRecipients = fund.recipients
.where((r) => !r.isReceived)
.toList();
final remainingSplits = fund.isOpen
? fund.amountOfSplits - claimedRecipients.length
: unclaimedRecipients.length;
return AlertDialog(
title: Row(
children: [
Icon(
Icons.account_balance_wallet,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text('Claim Fund'.tr()),
],
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Fund info
Text(
'${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
// Remaining amount
Text(
'fundEnvelopeRemainingWithSplits'.tr(
args: [
fund.remainingAmount.toStringAsFixed(2),
fund.currency,
remainingSplits.toString(),
],
),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
// Status indicator
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: fund.isOpen
? Colors.green.withOpacity(0.1)
: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: fund.isOpen
? Colors.green.withOpacity(0.3)
: Colors.blue.withOpacity(0.3),
),
),
child: Text(
fund.isOpen ? 'Open Fund'.tr() : 'Invite Only'.tr(),
style: TextStyle(
fontSize: 12,
color: fund.isOpen ? Colors.green : Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 16),
// Claimed recipients section
if (claimedRecipients.isNotEmpty) ...[
Text(
'Already Claimed'.tr(),
style: Theme.of(
context,
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
...claimedRecipients.map(
(recipient) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
Icon(Icons.check_circle, size: 16, color: Colors.green),
const SizedBox(width: 8),
Expanded(
child: Text(
recipient.recipientAccount?.nick ??
'fundEnvelopeUnknownUser'.tr(),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
decoration: TextDecoration.lineThrough,
color: Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
),
),
Text(
'${recipient.amount.toStringAsFixed(2)} ${fund.currency}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
),
),
const SizedBox(height: 12),
],
// Unclaimed recipients section
if (unclaimedRecipients.isNotEmpty) ...[
Text(
'Available to Claim'.tr(),
style: Theme.of(
context,
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
...unclaimedRecipients.map(
(recipient) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
Icon(
Icons.radio_button_unchecked,
size: 16,
color: Theme.of(
context,
).colorScheme.onSurfaceVariant.withOpacity(0.5),
),
const SizedBox(width: 8),
Expanded(
child: Text(
recipient.recipientAccount?.nick ??
'fundEnvelopeUnknownUser'.tr(),
style: Theme.of(context).textTheme.bodySmall,
),
),
Text(
'${recipient.amount.toStringAsFixed(2)} ${fund.currency}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
),
),
],
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Cancel'.tr()),
),
if (canClaim)
FilledButton.icon(
icon: const Icon(Icons.account_balance_wallet),
label: Text('Claim'.tr()),
onPressed: onClaim,
),
],
);
}
}

View File

@@ -0,0 +1,85 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'fund_envelope.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(walletFund)
final walletFundProvider = WalletFundFamily._();
final class WalletFundProvider
extends
$FunctionalProvider<
AsyncValue<SnWalletFund>,
SnWalletFund,
FutureOr<SnWalletFund>
>
with $FutureModifier<SnWalletFund>, $FutureProvider<SnWalletFund> {
WalletFundProvider._({
required WalletFundFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'walletFundProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$walletFundHash();
@override
String toString() {
return r'walletFundProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<SnWalletFund> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<SnWalletFund> create(Ref ref) {
final argument = this.argument as String;
return walletFund(ref, argument);
}
@override
bool operator ==(Object other) {
return other is WalletFundProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$walletFundHash() => r'566b74f1338de8825c19edbc987f942e05f65f49';
final class WalletFundFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<SnWalletFund>, String> {
WalletFundFamily._()
: super(
retry: null,
name: r'walletFundProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
WalletFundProvider call(String fundId) =>
WalletFundProvider._(argument: fundId, from: this);
@override
String toString() => r'walletFundProvider';
}