👽 Update the API request path due to the sepration of the wallet service

This commit is contained in:
2026-02-05 00:14:18 +08:00
parent 0237e457fc
commit 4b1c9b5820
7 changed files with 297 additions and 325 deletions

View File

@@ -30,7 +30,7 @@ part 'wallet.g.dart';
Future<SnWallet?> walletCurrent(Ref ref) async { Future<SnWallet?> walletCurrent(Ref ref) async {
try { try {
final apiClient = ref.watch(apiClientProvider); final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get('/pass/wallets'); final resp = await apiClient.get('/wallet/wallets');
return SnWallet.fromJson(resp.data); return SnWallet.fromJson(resp.data);
} catch (err) { } catch (err) {
if (err is DioException && err.response?.statusCode == 404) { if (err is DioException && err.response?.statusCode == 404) {
@@ -43,7 +43,7 @@ Future<SnWallet?> walletCurrent(Ref ref) async {
@riverpod @riverpod
Future<SnWalletStats> walletStats(Ref ref) async { Future<SnWalletStats> walletStats(Ref ref) async {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
final resp = await client.get('/pass/wallets/stats'); final resp = await client.get('/wallet/wallets/stats');
return SnWalletStats.fromJson(resp.data); return SnWalletStats.fromJson(resp.data);
} }
@@ -976,7 +976,7 @@ class TransactionListNotifier
final queryParams = {'offset': offset, 'take': pageSize}; final queryParams = {'offset': offset, 'take': pageSize};
final response = await client.get( final response = await client.get(
'/pass/wallets/transactions', '/wallet/wallets/transactions',
queryParameters: queryParams, queryParameters: queryParams,
); );
totalCount = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
@@ -1003,7 +1003,7 @@ class WalletFundsNotifier extends AsyncNotifier<PaginationState<SnWalletFund>>
final offset = fetchedCount; final offset = fetchedCount;
final response = await client.get( final response = await client.get(
'/pass/wallets/funds?offset=$offset&take=$pageSize', '/wallet/wallets/funds?offset=$offset&take=$pageSize',
); );
// Assuming total count header is present or we just check if list is empty // Assuming total count header is present or we just check if list is empty
final list = (response.data as List) final list = (response.data as List)
@@ -1031,7 +1031,7 @@ class WalletFundRecipientsNotifier
final offset = fetchedCount; final offset = fetchedCount;
final response = await client.get( final response = await client.get(
'/pass/wallets/funds/recipients?offset=$offset&take=$_pageSize', '/wallet/wallets/funds/recipients?offset=$offset&take=$_pageSize',
); );
final list = (response.data as List) final list = (response.data as List)
.map((e) => SnWalletFundRecipient.fromJson(e)) .map((e) => SnWalletFundRecipient.fromJson(e))
@@ -1047,7 +1047,7 @@ class WalletFundRecipientsNotifier
@riverpod @riverpod
Future<SnWalletFund> walletFund(Ref ref, String fundId) async { Future<SnWalletFund> walletFund(Ref ref, String fundId) async {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
final resp = await client.get('/pass/wallets/funds/$fundId'); final resp = await client.get('/wallet/wallets/funds/$fundId');
return SnWalletFund.fromJson(resp.data); return SnWalletFund.fromJson(resp.data);
} }
@@ -1239,7 +1239,7 @@ class WalletScreen extends HookConsumerWidget {
Future<void> createWallet() async { Future<void> createWallet() async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
try { try {
await client.post('/pass/wallets'); await client.post('/wallet/wallets');
ref.invalidate(walletCurrentProvider); ref.invalidate(walletCurrentProvider);
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
@@ -1715,7 +1715,7 @@ class WalletScreen extends HookConsumerWidget {
try { try {
showLoadingModal(context); showLoadingModal(context);
final resp = await client.post( final resp = await client.post(
'/pass/wallets/funds', '/wallet/wallets/funds',
data: fundData, data: fundData,
options: Options(headers: {'X-Noop': true}), options: Options(headers: {'X-Noop': true}),
); );
@@ -1723,7 +1723,7 @@ class WalletScreen extends HookConsumerWidget {
if (fund.status == 0) return; // Already created if (fund.status == 0) return; // Already created
final orderResp = await client.post( final orderResp = await client.post(
'/pass/wallets/funds/${fund.id}/order', '/wallet/wallets/funds/${fund.id}/order',
); );
final order = SnWalletOrder.fromJson(orderResp.data); final order = SnWalletOrder.fromJson(orderResp.data);
@@ -1763,7 +1763,7 @@ class WalletScreen extends HookConsumerWidget {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
try { try {
showLoadingModal(context); showLoadingModal(context);
await client.post('/pass/wallets/transfer', data: transferData); await client.post('/wallet/wallets/transfer', data: transferData);
if (context.mounted) hideLoadingModal(context); if (context.mounted) hideLoadingModal(context);

View File

@@ -31,7 +31,7 @@ class RestorePurchaseSheet extends HookConsumerWidget {
try { try {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
await client.post( await client.post(
'/pass/subscriptions/order/restore/${selectedProvider.value!}', '/wallet/subscriptions/order/restore/${selectedProvider.value!}',
data: {'order_id': orderIdController.text.trim()}, data: {'order_id': orderIdController.text.trim()},
); );
@@ -79,23 +79,22 @@ class RestorePurchaseSheet extends HookConsumerWidget {
hint: Text('selectProvider'.tr()), hint: Text('selectProvider'.tr()),
isExpanded: true, isExpanded: true,
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
items: items: providers.map((provider) {
providers.map((provider) { return DropdownMenuItem<String>(
return DropdownMenuItem<String>( value: provider,
value: provider, child: Row(
child: Row( children: [
children: [ getProviderIcon(
getProviderIcon( provider,
provider, size: 20,
size: 20, color: Theme.of(context).colorScheme.onSurface,
color: Theme.of(context).colorScheme.onSurface,
),
const Gap(12),
Text(getLocalizedProviderName(provider)),
],
), ),
); const Gap(12),
}).toList(), Text(getLocalizedProviderName(provider)),
],
),
);
}).toList(),
onChanged: (value) { onChanged: (value) {
selectedProvider.value = value; selectedProvider.value = value;
}, },
@@ -122,14 +121,13 @@ class RestorePurchaseSheet extends HookConsumerWidget {
// Restore Button // Restore Button
FilledButton( FilledButton(
onPressed: isLoading.value ? null : restorePurchase, onPressed: isLoading.value ? null : restorePurchase,
child: child: isLoading.value
isLoading.value ? const SizedBox(
? const SizedBox( height: 20,
height: 20, width: 20,
width: 20, child: CircularProgressIndicator(strokeWidth: 2),
child: CircularProgressIndicator(strokeWidth: 2), )
) : Text('restore'.tr()),
: Text('restore'.tr()),
), ),
const Gap(16), const Gap(16),
], ],

View File

@@ -30,7 +30,7 @@ part 'stellar_program_tab.g.dart';
Future<SnWalletSubscription?> accountStellarSubscription(Ref ref) async { Future<SnWalletSubscription?> accountStellarSubscription(Ref ref) async {
try { try {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
final resp = await client.get('/pass/subscriptions/fuzzy/solian.stellar'); final resp = await client.get('/wallet/subscriptions/fuzzy/solian.stellar');
return SnWalletSubscription.fromJson(resp.data); return SnWalletSubscription.fromJson(resp.data);
} catch (err) { } catch (err) {
if (err is DioException && err.response?.statusCode == 404) return null; if (err is DioException && err.response?.statusCode == 404) return null;
@@ -46,7 +46,7 @@ Future<List<SnWalletGift>> accountSentGifts(
}) async { }) async {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
final resp = await client.get( final resp = await client.get(
'/pass/subscriptions/gifts/sent?offset=$offset&take=$take', '/wallet/subscriptions/gifts/sent?offset=$offset&take=$take',
); );
return (resp.data as List).map((e) => SnWalletGift.fromJson(e)).toList(); return (resp.data as List).map((e) => SnWalletGift.fromJson(e)).toList();
} }
@@ -59,7 +59,7 @@ Future<List<SnWalletGift>> accountReceivedGifts(
}) async { }) async {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
final resp = await client.get( final resp = await client.get(
'/pass/subscriptions/gifts/received?offset=$offset&take=$take', '/wallet/subscriptions/gifts/received?offset=$offset&take=$take',
); );
return (resp.data as List).map((e) => SnWalletGift.fromJson(e)).toList(); return (resp.data as List).map((e) => SnWalletGift.fromJson(e)).toList();
} }
@@ -67,7 +67,7 @@ Future<List<SnWalletGift>> accountReceivedGifts(
@riverpod @riverpod
Future<SnWalletGift> accountGift(Ref ref, String giftId) async { Future<SnWalletGift> accountGift(Ref ref, String giftId) async {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
final resp = await client.get('/pass/subscriptions/gifts/$giftId'); final resp = await client.get('/wallet/subscriptions/gifts/$giftId');
return SnWalletGift.fromJson(resp.data); return SnWalletGift.fromJson(resp.data);
} }
@@ -379,7 +379,7 @@ class StellarProgramTab extends HookConsumerWidget {
showLoadingModal(context); showLoadingModal(context);
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
await client.post( await client.post(
'/pass/subscriptions/${membership.identifier}/cancel', '/wallet/subscriptions/${membership.identifier}/cancel',
); );
ref.invalidate(accountStellarSubscriptionProvider); ref.invalidate(accountStellarSubscriptionProvider);
ref.read(userInfoProvider.notifier).fetchUser(); ref.read(userInfoProvider.notifier).fetchUser();
@@ -680,7 +680,7 @@ class StellarProgramTab extends HookConsumerWidget {
try { try {
showLoadingModal(context); showLoadingModal(context);
final resp = await client.post( final resp = await client.post(
'/pass/subscriptions', '/wallet/subscriptions',
data: { data: {
'identifier': tierId, 'identifier': tierId,
'payment_method': 'solian.wallet', 'payment_method': 'solian.wallet',
@@ -692,7 +692,7 @@ class StellarProgramTab extends HookConsumerWidget {
final subscription = SnWalletSubscription.fromJson(resp.data); final subscription = SnWalletSubscription.fromJson(resp.data);
if (subscription.status == 1) return; if (subscription.status == 1) return;
final orderResp = await client.post( final orderResp = await client.post(
'/pass/subscriptions/${subscription.identifier}/order', '/wallet/subscriptions/${subscription.identifier}/order',
); );
final order = SnWalletOrder.fromJson(orderResp.data); final order = SnWalletOrder.fromJson(orderResp.data);
@@ -1188,7 +1188,7 @@ class StellarProgramTab extends HookConsumerWidget {
try { try {
showLoadingModal(context); showLoadingModal(context);
final resp = await client.post( final resp = await client.post(
'/pass/subscriptions/gifts/purchase', '/wallet/subscriptions/gifts/purchase',
data: { data: {
'subscription_identifier': subscriptionId, 'subscription_identifier': subscriptionId,
'recipient_id': ?recipientId, 'recipient_id': ?recipientId,
@@ -1204,7 +1204,7 @@ class StellarProgramTab extends HookConsumerWidget {
if (gift.status == 1) return; // Already paid if (gift.status == 1) return; // Already paid
final orderResp = await client.post( final orderResp = await client.post(
'/pass/subscriptions/gifts/${gift.id}/order', '/wallet/subscriptions/gifts/${gift.id}/order',
); );
final order = SnWalletOrder.fromJson(orderResp.data); final order = SnWalletOrder.fromJson(orderResp.data);
@@ -1226,7 +1226,7 @@ class StellarProgramTab extends HookConsumerWidget {
// Get the updated gift // Get the updated gift
final giftResp = await client.get( final giftResp = await client.get(
'/pass/subscriptions/gifts/${gift.id}', '/wallet/subscriptions/gifts/${gift.id}',
); );
final updatedGift = SnWalletGift.fromJson(giftResp.data); final updatedGift = SnWalletGift.fromJson(giftResp.data);
@@ -1328,7 +1328,7 @@ class StellarProgramTab extends HookConsumerWidget {
// First check if gift can be redeemed // First check if gift can be redeemed
final checkResp = await client.get( final checkResp = await client.get(
'/pass/subscriptions/gifts/check/$giftCode', '/wallet/subscriptions/gifts/check/$giftCode',
); );
final checkData = checkResp.data as Map<String, dynamic>; final checkData = checkResp.data as Map<String, dynamic>;
@@ -1340,7 +1340,7 @@ class StellarProgramTab extends HookConsumerWidget {
// Redeem the gift // Redeem the gift
await client.post( await client.post(
'/pass/subscriptions/gifts/redeem', '/wallet/subscriptions/gifts/redeem',
data: {'gift_code': giftCode}, data: {'gift_code': giftCode},
); );
@@ -1384,7 +1384,7 @@ class StellarProgramTab extends HookConsumerWidget {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
try { try {
showLoadingModal(context); showLoadingModal(context);
await client.post('/pass/subscriptions/gifts/${gift.id}/cancel'); await client.post('/wallet/subscriptions/gifts/${gift.id}/cancel');
ref.invalidate(accountSentGiftsProvider); ref.invalidate(accountSentGiftsProvider);
if (context.mounted) { if (context.mounted) {
hideLoadingModal(context); hideLoadingModal(context);

View File

@@ -66,21 +66,20 @@ class PaymentOverlay extends HookConsumerWidget {
isScrollControlled: true, isScrollControlled: true,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
useSafeArea: true, useSafeArea: true,
builder: builder: (context) => PaymentOverlay(
(context) => PaymentOverlay( order: order,
order: order, enableBiometric: enableBiometric,
enableBiometric: enableBiometric, onPaymentSuccess: (completedOrder) {
onPaymentSuccess: (completedOrder) { Navigator.of(context).pop(completedOrder);
Navigator.of(context).pop(completedOrder); },
}, onPaymentError: (err) {
onPaymentError: (err) { Navigator.of(context).pop();
Navigator.of(context).pop(); showErrorAlert(err);
showErrorAlert(err); },
}, onCancel: () {
onCancel: () { Navigator.of(context).pop();
Navigator.of(context).pop(); },
}, ),
),
); );
} }
} }
@@ -241,7 +240,7 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> {
try { try {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final response = await client.post( final response = await client.post(
'/pass/orders/${widget.order.id}/pay', '/wallet/orders/${widget.order.id}/pay',
data: {'pin_code': pin}, data: {'pin_code': pin},
); );
@@ -415,46 +414,42 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> {
Widget _buildBiometricAuth() { Widget _buildBiometricAuth() {
return SingleChildScrollView( return SingleChildScrollView(
child: child: Column(
Column( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, children: [
children: [ Icon(Symbols.fingerprint, size: 48),
Icon(Symbols.fingerprint, size: 48), const Gap(16),
const Gap(16), Text(
Text( 'useBiometricToConfirm'.tr(),
'useBiometricToConfirm'.tr(), style: Theme.of(
style: Theme.of( context,
context, ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500),
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500), textAlign: TextAlign.center,
textAlign: TextAlign.center, ),
), Text(
Text( 'The biometric data will only be processed on your device',
'The biometric data will only be processed on your device', style: Theme.of(context).textTheme.bodyMedium?.copyWith(
style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant,
color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 11,
fontSize: 11, ),
), textAlign: TextAlign.center,
textAlign: TextAlign.center, ).opacity(0.75),
).opacity(0.75), const Gap(28),
const Gap(28), ElevatedButton.icon(
ElevatedButton.icon( onPressed: _authenticateWithBiometric,
onPressed: _authenticateWithBiometric, icon: const Icon(Symbols.fingerprint),
icon: const Icon(Symbols.fingerprint), label: Text('authenticateNow'.tr()),
label: Text('authenticateNow'.tr()), style: ElevatedButton.styleFrom(
style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
padding: const EdgeInsets.symmetric( ),
horizontal: 24, ),
vertical: 12, TextButton(
), onPressed: () => _fallbackToPinMode(null),
), child: Text('usePinInstead'.tr()),
), ),
TextButton( ],
onPressed: () => _fallbackToPinMode(null), ).center(),
child: Text('usePinInstead'.tr()),
),
],
).center(),
); );
} }

View File

@@ -232,7 +232,7 @@ class ComposeFundSheet extends HookConsumerWidget {
showLoadingModal(context); showLoadingModal(context);
final resp = await client.post( final resp = await client.post(
'/pass/wallets/funds', '/wallet/wallets/funds',
data: result, data: result,
options: Options( options: Options(
headers: {'X-Noop': true}, headers: {'X-Noop': true},
@@ -253,7 +253,7 @@ class ComposeFundSheet extends HookConsumerWidget {
} }
final orderResp = await client.post( final orderResp = await client.post(
'/pass/wallets/funds/${fund.id}/order', '/wallet/wallets/funds/${fund.id}/order',
); );
final order = SnWalletOrder.fromJson( final order = SnWalletOrder.fromJson(
orderResp.data, orderResp.data,
@@ -284,7 +284,7 @@ class ComposeFundSheet extends HookConsumerWidget {
// Return the created fund // Return the created fund
final updatedResp = await client.get( final updatedResp = await client.get(
'/pass/wallets/funds/${fund.id}', '/wallet/wallets/funds/${fund.id}',
); );
final updatedFund = final updatedFund =
SnWalletFund.fromJson( SnWalletFund.fromJson(

View File

@@ -311,7 +311,7 @@ class PostAwardSheet extends HookConsumerWidget {
final orderId = awardResponse.data['order_id'] as String; final orderId = awardResponse.data['order_id'] as String;
// Fetch order details // Fetch order details
final orderResponse = await client.get('/pass/orders/$orderId'); final orderResponse = await client.get('/wallet/orders/$orderId');
final order = SnWalletOrder.fromJson(orderResponse.data); final order = SnWalletOrder.fromJson(orderResponse.data);
if (context.mounted) { if (context.mounted) {

View File

@@ -12,7 +12,7 @@ part 'fund_envelope.g.dart';
@riverpod @riverpod
Future<SnWalletFund> walletFund(Ref ref, String fundId) async { Future<SnWalletFund> walletFund(Ref ref, String fundId) async {
final apiClient = ref.watch(apiClientProvider); final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get('/pass/wallets/funds/$fundId'); final resp = await apiClient.get('/wallet/wallets/funds/$fundId');
return SnWalletFund.fromJson(resp.data); return SnWalletFund.fromJson(resp.data);
} }
@@ -36,202 +36,181 @@ class FundEnvelopeWidget extends HookConsumerWidget {
width: maxWidth, width: maxWidth,
margin: margin ?? const EdgeInsets.symmetric(vertical: 8), margin: margin ?? const EdgeInsets.symmetric(vertical: 8),
child: fundAsync.when( child: fundAsync.when(
loading: loading: () => Card(
() => Card( margin: EdgeInsets.zero,
margin: EdgeInsets.zero, child: const Padding(
child: const Padding( padding: EdgeInsets.all(16),
padding: EdgeInsets.all(16), child: Center(child: CircularProgressIndicator()),
child: Center(child: CircularProgressIndicator()), ),
), ),
), error: (error, stack) => Card(
error: margin: EdgeInsets.zero,
(error, stack) => Card( child: Padding(
margin: EdgeInsets.zero, padding: const EdgeInsets.all(16),
child: Padding( child: Column(
padding: const EdgeInsets.all(16), children: [
child: Column( Icon(
children: [ Icons.error_outline,
Icon( color: Theme.of(context).colorScheme.error,
Icons.error_outline,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(height: 8),
Text(
'fundEnvelopeLoadFailed'.tr(),
style: TextStyle(
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, data: (fund) => Card(
clipBehavior: Clip.antiAlias, margin: EdgeInsets.zero,
child: InkWell( clipBehavior: Clip.antiAlias,
onTap: () => _showClaimDialog(context, ref, fund), child: InkWell(
borderRadius: BorderRadius.circular(8), onTap: () => _showClaimDialog(context, ref, fund),
child: Padding( borderRadius: BorderRadius.circular(8),
padding: const EdgeInsets.all(16), child: Padding(
child: Column( padding: const EdgeInsets.all(16),
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Fund title and status
Row(
children: [ children: [
// Fund title and status Icon(
Row( Icons.account_balance_wallet,
children: [ color: Theme.of(context).colorScheme.primary,
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), 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 // Amount information
Row( Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Column( Text(
crossAxisAlignment: CrossAxisAlignment.start, '${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}',
children: [ style: Theme.of(context).textTheme.headlineSmall
Text( ?.copyWith(
'${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}',
style: Theme.of(
context,
).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary, 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,
],
), ),
const SizedBox(height: 4), style: Theme.of(context).textTheme.bodySmall
if (fund.remainingAmount != fund.totalAmount) ?.copyWith(
Text( color: Theme.of(
'fundEnvelopeRemaining'.tr( context,
args: [ ).colorScheme.secondary,
fund.remainingAmount.toStringAsFixed(2),
fund.currency,
],
),
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(
color:
Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
Text( Text(
'fundEnvelopeSplit'.tr( 'fundEnvelopeSplit'.tr(
args: [ args: [
fund.splitType == 0 fund.splitType == 0
? 'fundEnvelopeSplitEvenly'.tr() ? 'fundEnvelopeSplitEvenly'.tr()
: 'fundEnvelopeSplitRandomly'.tr(), : 'fundEnvelopeSplitRandomly'.tr(),
], ],
), ),
style: Theme.of( style: Theme.of(context).textTheme.bodySmall
context, ?.copyWith(
).textTheme.bodySmall?.copyWith(
color: Theme.of(context) color: Theme.of(context)
.textTheme .textTheme
.bodySmall .bodySmall
?.color ?.color
?.withOpacity(0.7), ?.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,
),
), ),
], ],
), ),
], ],
), ),
),
// 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,
),
),
],
),
],
), ),
), ),
),
),
), ),
); );
} }
@@ -243,26 +222,25 @@ class FundEnvelopeWidget extends HookConsumerWidget {
) { ) {
showDialog( showDialog(
context: context, context: context,
builder: builder: (dialogContext) => FundClaimDialog(
(dialogContext) => FundClaimDialog( fund: fund,
fund: fund, onClaim: () async {
onClaim: () async { try {
try { final apiClient = ref.read(apiClientProvider);
final apiClient = ref.read(apiClientProvider); await apiClient.post('/wallet/wallets/funds/${fund.id}/receive');
await apiClient.post('/pass/wallets/funds/${fund.id}/receive');
// Refresh the fund data after claiming // Refresh the fund data after claiming
ref.invalidate(walletFundProvider(fund.id)); ref.invalidate(walletFundProvider(fund.id));
if (dialogContext.mounted) { if (dialogContext.mounted) {
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop();
showSnackBar('fundEnvelopeClaimSuccess'.tr()); showSnackBar('fundEnvelopeClaimSuccess'.tr());
} }
} catch (e) { } catch (e) {
showErrorAlert(e); showErrorAlert(e);
} }
}, },
), ),
); );
} }
@@ -343,8 +321,9 @@ class FundEnvelopeWidget extends HookConsumerWidget {
Widget _buildRecipientsOverview(BuildContext context, SnWalletFund fund) { Widget _buildRecipientsOverview(BuildContext context, SnWalletFund fund) {
final claimedCount = fund.recipients.where((r) => r.isReceived).length; final claimedCount = fund.recipients.where((r) => r.isReceived).length;
final totalCount = final totalCount = fund.isOpen
fund.isOpen ? fund.amountOfSplits : fund.recipients.length; ? fund.amountOfSplits
: fund.recipients.length;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -362,8 +341,9 @@ class FundEnvelopeWidget extends HookConsumerWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
LinearProgressIndicator( LinearProgressIndicator(
value: totalCount > 0 ? claimedCount / totalCount : 0, value: totalCount > 0 ? claimedCount / totalCount : 0,
backgroundColor: backgroundColor: Theme.of(
Theme.of(context).colorScheme.surfaceContainerHighest, context,
).colorScheme.surfaceContainerHighest,
), ),
], ],
); );
@@ -436,15 +416,16 @@ class FundClaimDialog extends HookConsumerWidget {
!isExpired && hasRemainingAmount && !hasUserClaimed && userAbleToClaim; !isExpired && hasRemainingAmount && !hasUserClaimed && userAbleToClaim;
// Get claimed recipients for display // Get claimed recipients for display
final claimedRecipients = final claimedRecipients = fund.recipients
fund.recipients.where((r) => r.isReceived).toList(); .where((r) => r.isReceived)
final unclaimedRecipients = .toList();
fund.recipients.where((r) => !r.isReceived).toList(); final unclaimedRecipients = fund.recipients
.where((r) => !r.isReceived)
.toList();
final remainingSplits = final remainingSplits = fund.isOpen
fund.isOpen ? fund.amountOfSplits - claimedRecipients.length
? fund.amountOfSplits - claimedRecipients.length : unclaimedRecipients.length;
: unclaimedRecipients.length;
return AlertDialog( return AlertDialog(
title: Row( title: Row(
@@ -491,16 +472,14 @@ class FundClaimDialog extends HookConsumerWidget {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: fund.isOpen
fund.isOpen ? Colors.green.withOpacity(0.1)
? Colors.green.withOpacity(0.1) : Colors.blue.withOpacity(0.1),
: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: color: fund.isOpen
fund.isOpen ? Colors.green.withOpacity(0.3)
? Colors.green.withOpacity(0.3) : Colors.blue.withOpacity(0.3),
: Colors.blue.withOpacity(0.3),
), ),
), ),
child: Text( child: Text(
@@ -535,13 +514,13 @@ class FundClaimDialog extends HookConsumerWidget {
child: Text( child: Text(
recipient.recipientAccount?.nick ?? recipient.recipientAccount?.nick ??
'fundEnvelopeUnknownUser'.tr(), 'fundEnvelopeUnknownUser'.tr(),
style: Theme.of( style: Theme.of(context).textTheme.bodySmall
context, ?.copyWith(
).textTheme.bodySmall?.copyWith( decoration: TextDecoration.lineThrough,
decoration: TextDecoration.lineThrough, color: Theme.of(
color: context,
Theme.of(context).colorScheme.onSurfaceVariant, ).colorScheme.onSurfaceVariant,
), ),
), ),
), ),
Text( Text(