import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/utils/number_format.dart'; import '../../../data/models/finance/staking_order_item.dart'; import '../../../providers/asset_provider.dart'; import '../../widgets/common/app_refresh_indicator.dart'; /// 锁仓账户 Tab — 对齐 Web AssetsView STAKING Tab class AssetStakingTab extends StatelessWidget { const AssetStakingTab({super.key, required this.state, required this.notifier}); final AssetState state; final AssetNotifier notifier; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final l10n = AppLocalizations.of(context)!; final obscure = state.obscureBalance; final released = double.tryParse(state.stakingOverviewReleased) ?? 0; final locked = double.tryParse(state.stakingOverviewLocked) ?? 0; final available = double.tryParse(state.stakingOverviewAvailable) ?? 0; final totalLocked = locked + available; final releasedDisplay = obscure ? '******' : formatPrice(released, decimalPlaces: 2); final totalDisplay = obscure ? '******' : formatPrice(totalLocked, decimalPlaces: 2); return AppRefreshIndicator( onRefresh: notifier.refreshStakingTab, child: ListView( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 20, 16, 0), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).brightness == Brightness.dark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary, borderRadius: BorderRadius.circular(12), border: Theme.of(context).brightness == Brightness.dark ? null : Border.all(color: AppColors.lightBorder, width: 0.5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( l10n.assetsReleaseTotal, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 13, ), ), const SizedBox(width: 6), GestureDetector( onTap: notifier.toggleObscure, child: Icon( obscure ? Icons.visibility_off_outlined : Icons.visibility_outlined, size: 16, color: cs.onSurface.withAlpha(153), ), ), ], ), const SizedBox(height: 8), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( releasedDisplay, style: TextStyle( color: cs.onSurface, fontSize: 32, fontWeight: FontWeight.w700, letterSpacing: -0.5, ), ), const SizedBox(width: 6), Padding( padding: const EdgeInsets.only(bottom: 5), child: Text( l10n.assetsIbitUnit, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 14, ), ), ), ], ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( l10n.assetsTotalLockedBalance, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 13, ), ), Text( totalDisplay, style: TextStyle( color: cs.onSurface, fontSize: 13, fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: _StakingActionButton( label: l10n.depositCoin, primary: true, onTap: () => context.push('/asset/deposit'), ), ), const SizedBox(width: 8), Expanded( child: _StakingActionButton( label: l10n.withdrawCoin, onTap: () => context.push('/asset/withdraw'), ), ), const SizedBox(width: 8), Expanded( child: _StakingActionButton( label: l10n.transfer, onTap: () => context.push('/asset/transfer?from=SPOT&to=SWAP'), ), ), ], ), ], ), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 20, 16, 8), child: Row( children: [ Expanded( child: Text( l10n.assetsMyStakingOrders, style: TextStyle( color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w600, ), ), ), _StakingStatusFilter( value: state.stakingOrderStatusFilter, loading: state.stakingOrdersLoading, onChanged: notifier.setStakingOrderStatusFilter, ), ], ), ), if (state.stakingOrdersLoading && state.stakingOrders.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 32), child: Center( child: SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2, color: cs.onSurface.withAlpha(120), ), ), ), ) else if (state.stakingOrders.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 32), child: Center( child: Text( l10n.noData, style: TextStyle( color: cs.onSurface.withAlpha(120), fontSize: 14, ), ), ), ) else ...state.stakingOrders.map( (order) => _StakingOrderCard(order: order, obscure: obscure), ), const SizedBox(height: 32), ], ), ); } } class _StakingActionButton extends StatelessWidget { const _StakingActionButton({ required this.label, required this.onTap, this.primary = false, }); final String label; final VoidCallback onTap; final bool primary; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return GestureDetector( onTap: onTap, child: Container( height: 40, alignment: Alignment.center, decoration: BoxDecoration( color: primary ? AppColors.brand : cs.onSurface.withAlpha(20), borderRadius: BorderRadius.circular(20), ), child: Text( label, style: TextStyle( color: primary ? Colors.white : cs.onSurface, fontSize: 13, fontWeight: FontWeight.w600, ), ), ), ); } } class _StakingStatusFilter extends StatelessWidget { const _StakingStatusFilter({ required this.value, required this.loading, required this.onChanged, }); final StakingOrderStatusFilter value; final bool loading; final ValueChanged onChanged; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final cs = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( border: Border.all(color: cs.outline.withAlpha(80)), borderRadius: BorderRadius.circular(8), ), child: DropdownButtonHideUnderline( child: DropdownButton( value: value, isDense: true, icon: Icon(Icons.expand_more, size: 18, color: cs.onSurface.withAlpha(153)), items: [ DropdownMenuItem( value: StakingOrderStatusFilter.all, child: Text(l10n.all, style: const TextStyle(fontSize: 13)), ), DropdownMenuItem( value: StakingOrderStatusFilter.subscribing, child: Text(l10n.assetsSubscribing, style: const TextStyle(fontSize: 13)), ), DropdownMenuItem( value: StakingOrderStatusFilter.releasing, child: Text(l10n.assetsReleasing, style: const TextStyle(fontSize: 13)), ), DropdownMenuItem( value: StakingOrderStatusFilter.completed, child: Text(l10n.completed, style: const TextStyle(fontSize: 13)), ), ], onChanged: loading ? null : (next) { if (next != null) { onChanged(next); } }, ), ), ); } } class _StakingOrderCard extends StatelessWidget { const _StakingOrderCard({required this.order, required this.obscure}); final StakingOrderItem order; final bool obscure; String _fmtAmount(double value) { if (obscure) { return '******'; } return formatPrice(value, decimalPlaces: 2); } String _statusLabel(AppLocalizations l10n) { if (order.status == 0) { return l10n.assetsSubscribing; } if (order.status == 2) { return l10n.completed; } return l10n.assetsReleasing; } Color _statusColor() { if (order.status == 0) { return AppColors.brand; } if (order.status == 2) { return Colors.grey; } return Colors.orange; } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final l10n = AppLocalizations.of(context)!; final isDark = Theme.of(context).brightness == Brightness.dark; final coin = order.coinUnit.isNotEmpty ? order.coinUnit : l10n.assetsIbitUnit.toUpperCase(); final amount = double.tryParse(order.amount) ?? 0; final released = double.tryParse(order.releasedAmount) ?? 0; return Container( margin: const EdgeInsets.fromLTRB(16, 0, 16, 10), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary, borderRadius: BorderRadius.circular(12), border: isDark ? null : Border.all(color: AppColors.lightBorder, width: 0.5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${l10n.assetsStakingOrderNo}: ORD${order.id}', style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 12, ), ), const SizedBox(height: 12), _StakingOrderRow( label: l10n.assetsStakingTotal, value: '${_fmtAmount(amount)} $coin', ), _StakingOrderRow( label: l10n.assetsReleased, value: '${_fmtAmount(released)} $coin', ), _StakingOrderRow( label: l10n.assetsPendingRelease, value: '${_fmtAmount(order.pendingAmount)} $coin', ), _StakingOrderRow( label: l10n.startTime, value: order.displayStartTime, ), _StakingOrderRow( label: l10n.statusLabel, value: _statusLabel(l10n), valueColor: _statusColor(), ), ], ), ); } } class _StakingOrderRow extends StatelessWidget { const _StakingOrderRow({ required this.label, required this.value, this.valueColor, }); final String label; final String value; final Color? valueColor; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13), ), Flexible( child: Text( value, textAlign: TextAlign.right, style: TextStyle( color: valueColor ?? cs.onSurface, fontSize: 13, fontWeight: FontWeight.w500, ), ), ), ], ), ); } }