|
@@ -1,5 +1,4 @@
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
-import 'package:go_router/go_router.dart';
|
|
|
|
|
|
|
|
|
|
import '../../../core/l10n/app_localizations.dart';
|
|
import '../../../core/l10n/app_localizations.dart';
|
|
|
import '../../../core/theme/app_colors.dart';
|
|
import '../../../core/theme/app_colors.dart';
|
|
@@ -10,7 +9,8 @@ import '../../widgets/common/app_refresh_indicator.dart';
|
|
|
|
|
|
|
|
/// 锁仓账户 Tab — 对齐 Web AssetsView STAKING Tab
|
|
/// 锁仓账户 Tab — 对齐 Web AssetsView STAKING Tab
|
|
|
class AssetStakingTab extends StatelessWidget {
|
|
class AssetStakingTab extends StatelessWidget {
|
|
|
- const AssetStakingTab({super.key, required this.state, required this.notifier});
|
|
|
|
|
|
|
+ const AssetStakingTab(
|
|
|
|
|
+ {super.key, required this.state, required this.notifier});
|
|
|
final AssetState state;
|
|
final AssetState state;
|
|
|
final AssetNotifier notifier;
|
|
final AssetNotifier notifier;
|
|
|
|
|
|
|
@@ -116,34 +116,6 @@ class AssetStakingTab extends StatelessWidget {
|
|
|
),
|
|
),
|
|
|
],
|
|
],
|
|
|
),
|
|
),
|
|
|
- const SizedBox(height: 16),
|
|
|
|
|
- if (false) //先隐藏划转按钮
|
|
|
|
|
- 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'),
|
|
|
|
|
- ),
|
|
|
|
|
- ),
|
|
|
|
|
- ],
|
|
|
|
|
- ),
|
|
|
|
|
],
|
|
],
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
@@ -208,41 +180,6 @@ class AssetStakingTab extends StatelessWidget {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-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 {
|
|
class _StakingStatusFilter extends StatelessWidget {
|
|
|
const _StakingStatusFilter({
|
|
const _StakingStatusFilter({
|
|
|
required this.value,
|
|
required this.value,
|
|
@@ -267,7 +204,8 @@ class _StakingStatusFilter extends StatelessWidget {
|
|
|
child: DropdownButton<StakingOrderStatusFilter>(
|
|
child: DropdownButton<StakingOrderStatusFilter>(
|
|
|
value: value,
|
|
value: value,
|
|
|
isDense: true,
|
|
isDense: true,
|
|
|
- icon: Icon(Icons.expand_more, size: 18, color: cs.onSurface.withAlpha(153)),
|
|
|
|
|
|
|
+ icon: Icon(Icons.expand_more,
|
|
|
|
|
+ size: 18, color: cs.onSurface.withAlpha(153)),
|
|
|
items: [
|
|
items: [
|
|
|
DropdownMenuItem(
|
|
DropdownMenuItem(
|
|
|
value: StakingOrderStatusFilter.all,
|
|
value: StakingOrderStatusFilter.all,
|
|
@@ -275,11 +213,13 @@ class _StakingStatusFilter extends StatelessWidget {
|
|
|
),
|
|
),
|
|
|
DropdownMenuItem(
|
|
DropdownMenuItem(
|
|
|
value: StakingOrderStatusFilter.subscribing,
|
|
value: StakingOrderStatusFilter.subscribing,
|
|
|
- child: Text(l10n.assetsSubscribing, style: const TextStyle(fontSize: 13)),
|
|
|
|
|
|
|
+ child: Text(l10n.assetsSubscribing,
|
|
|
|
|
+ style: const TextStyle(fontSize: 13)),
|
|
|
),
|
|
),
|
|
|
DropdownMenuItem(
|
|
DropdownMenuItem(
|
|
|
value: StakingOrderStatusFilter.releasing,
|
|
value: StakingOrderStatusFilter.releasing,
|
|
|
- child: Text(l10n.assetsReleasing, style: const TextStyle(fontSize: 13)),
|
|
|
|
|
|
|
+ child: Text(l10n.assetsReleasing,
|
|
|
|
|
+ style: const TextStyle(fontSize: 13)),
|
|
|
),
|
|
),
|
|
|
DropdownMenuItem(
|
|
DropdownMenuItem(
|
|
|
value: StakingOrderStatusFilter.completed,
|
|
value: StakingOrderStatusFilter.completed,
|
|
@@ -304,11 +244,24 @@ class _StakingOrderCard extends StatelessWidget {
|
|
|
final StakingOrderItem order;
|
|
final StakingOrderItem order;
|
|
|
final bool obscure;
|
|
final bool obscure;
|
|
|
|
|
|
|
|
- String _fmtAmount(double value) {
|
|
|
|
|
|
|
+ String _fmtAmount(double value, {int decimals = 2}) {
|
|
|
if (obscure) {
|
|
if (obscure) {
|
|
|
return '******';
|
|
return '******';
|
|
|
}
|
|
}
|
|
|
- return formatPrice(value, decimalPlaces: 2);
|
|
|
|
|
|
|
+ return formatPrice(value, decimalPlaces: decimals);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String _fmtReleasedCompact(double value) {
|
|
|
|
|
+ if (obscure) {
|
|
|
|
|
+ return '******';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value == value.truncateToDouble()) {
|
|
|
|
|
+ return value.toInt().toString();
|
|
|
|
|
+ }
|
|
|
|
|
+ return value
|
|
|
|
|
+ .toStringAsFixed(8)
|
|
|
|
|
+ .replaceAll(RegExp(r'0+$'), '')
|
|
|
|
|
+ .replaceAll(RegExp(r'\.$'), '');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
String _statusLabel(AppLocalizations l10n) {
|
|
String _statusLabel(AppLocalizations l10n) {
|
|
@@ -321,14 +274,71 @@ class _StakingOrderCard extends StatelessWidget {
|
|
|
return l10n.assetsReleasing;
|
|
return l10n.assetsReleasing;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Color _statusColor() {
|
|
|
|
|
|
|
+ Color _statusTextColor() {
|
|
|
if (order.status == 0) {
|
|
if (order.status == 0) {
|
|
|
return AppColors.brand;
|
|
return AppColors.brand;
|
|
|
}
|
|
}
|
|
|
if (order.status == 2) {
|
|
if (order.status == 2) {
|
|
|
- return Colors.grey;
|
|
|
|
|
|
|
+ return const Color(0xFF8E949F);
|
|
|
}
|
|
}
|
|
|
- return Colors.orange;
|
|
|
|
|
|
|
+ return const Color(0xFF12161C);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Color _statusBackgroundColor() {
|
|
|
|
|
+ if (order.status == 0) {
|
|
|
|
|
+ return const Color(0xFF12161C);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (order.status == 2) {
|
|
|
|
|
+ return const Color(0xFF12161C);
|
|
|
|
|
+ }
|
|
|
|
|
+ return AppColors.brand;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String _displayDate(String? raw) {
|
|
|
|
|
+ if (raw == null || raw.isEmpty) {
|
|
|
|
|
+ return '-';
|
|
|
|
|
+ }
|
|
|
|
|
+ final normalized = raw.replaceFirst('T', ' ');
|
|
|
|
|
+ return normalized.length >= 10 ? normalized.substring(0, 10) : normalized;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String _lockDaysText() {
|
|
|
|
|
+ final startRaw = order.stakingTime ?? order.createTime;
|
|
|
|
|
+ final endRaw = order.unlockTime;
|
|
|
|
|
+ if (startRaw == null ||
|
|
|
|
|
+ endRaw == null ||
|
|
|
|
|
+ startRaw.isEmpty ||
|
|
|
|
|
+ endRaw.isEmpty) {
|
|
|
|
|
+ return '--';
|
|
|
|
|
+ }
|
|
|
|
|
+ final start = DateTime.tryParse(startRaw);
|
|
|
|
|
+ final end = DateTime.tryParse(endRaw);
|
|
|
|
|
+ if (start == null || end == null) {
|
|
|
|
|
+ return '--';
|
|
|
|
|
+ }
|
|
|
|
|
+ final days = end.difference(start).inDays.abs();
|
|
|
|
|
+ return days > 0 ? '$days天' : '--';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ double _releaseProgress(double amount, double released) {
|
|
|
|
|
+ if (!amount.isFinite || amount <= 0) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ final ratio = released / amount;
|
|
|
|
|
+ if (!ratio.isFinite) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ return ratio.clamp(0.0, 1.0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Color _progressColor() {
|
|
|
|
|
+ if (order.status == 2) {
|
|
|
|
|
+ return const Color(0xFF62656C);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (order.status == 1) {
|
|
|
|
|
+ return AppColors.brand;
|
|
|
|
|
+ }
|
|
|
|
|
+ return Colors.transparent;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
@@ -336,49 +346,92 @@ class _StakingOrderCard extends StatelessWidget {
|
|
|
final cs = Theme.of(context).colorScheme;
|
|
final cs = Theme.of(context).colorScheme;
|
|
|
final l10n = AppLocalizations.of(context)!;
|
|
final l10n = AppLocalizations.of(context)!;
|
|
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
|
- final coin = order.coinUnit.isNotEmpty ? order.coinUnit : l10n.assetsIbitUnit.toUpperCase();
|
|
|
|
|
|
|
+ final coin = order.coinUnit.isNotEmpty
|
|
|
|
|
+ ? order.coinUnit
|
|
|
|
|
+ : l10n.assetsIbitUnit.toUpperCase();
|
|
|
final amount = double.tryParse(order.amount) ?? 0;
|
|
final amount = double.tryParse(order.amount) ?? 0;
|
|
|
final released = double.tryParse(order.releasedAmount) ?? 0;
|
|
final released = double.tryParse(order.releasedAmount) ?? 0;
|
|
|
|
|
+ final progress = _releaseProgress(amount, released);
|
|
|
|
|
|
|
|
return Container(
|
|
return Container(
|
|
|
margin: const EdgeInsets.fromLTRB(16, 0, 16, 10),
|
|
margin: const EdgeInsets.fromLTRB(16, 0, 16, 10),
|
|
|
- padding: const EdgeInsets.all(16),
|
|
|
|
|
|
|
+ padding: const EdgeInsets.fromLTRB(14, 14, 14, 12),
|
|
|
decoration: BoxDecoration(
|
|
decoration: BoxDecoration(
|
|
|
color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary,
|
|
color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary,
|
|
|
- borderRadius: BorderRadius.circular(12),
|
|
|
|
|
- border: isDark ? null : Border.all(color: AppColors.lightBorder, width: 0.5),
|
|
|
|
|
|
|
+ borderRadius: BorderRadius.circular(16),
|
|
|
|
|
+ border: isDark
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : Border.all(color: AppColors.lightBorder, width: 0.5),
|
|
|
),
|
|
),
|
|
|
child: Column(
|
|
child: Column(
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
children: [
|
|
children: [
|
|
|
- Text(
|
|
|
|
|
- '${l10n.assetsStakingOrderNo}: ORD${order.id}',
|
|
|
|
|
- style: TextStyle(
|
|
|
|
|
- color: cs.onSurface.withAlpha(153),
|
|
|
|
|
- fontSize: 12,
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ Row(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ '${l10n.assetsStakingOrderNo}: ORD${order.id}',
|
|
|
|
|
+ style: TextStyle(
|
|
|
|
|
+ color: cs.onSurface,
|
|
|
|
|
+ fontSize: 13,
|
|
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ Container(
|
|
|
|
|
+ padding:
|
|
|
|
|
+ const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
|
|
|
|
+ decoration: BoxDecoration(
|
|
|
|
|
+ color: _statusBackgroundColor(),
|
|
|
|
|
+ borderRadius: BorderRadius.circular(999),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ _statusLabel(l10n),
|
|
|
|
|
+ style: TextStyle(
|
|
|
|
|
+ color: _statusTextColor(),
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
|
|
+ height: 1,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
),
|
|
),
|
|
|
- const SizedBox(height: 12),
|
|
|
|
|
|
|
+ const SizedBox(height: 14),
|
|
|
_StakingOrderRow(
|
|
_StakingOrderRow(
|
|
|
- label: l10n.assetsStakingTotal,
|
|
|
|
|
- value: '${_fmtAmount(amount)} $coin',
|
|
|
|
|
|
|
+ label: l10n.orderTime,
|
|
|
|
|
+ value: _displayDate(order.stakingTime ?? order.createTime),
|
|
|
),
|
|
),
|
|
|
_StakingOrderRow(
|
|
_StakingOrderRow(
|
|
|
- label: l10n.assetsReleased,
|
|
|
|
|
- value: '${_fmtAmount(released)} $coin',
|
|
|
|
|
|
|
+ label: l10n.assetsStakingTotal,
|
|
|
|
|
+ value: '${_fmtAmount(amount, decimals: 0)} $coin',
|
|
|
),
|
|
),
|
|
|
_StakingOrderRow(
|
|
_StakingOrderRow(
|
|
|
- label: l10n.assetsPendingRelease,
|
|
|
|
|
- value: '${_fmtAmount(order.pendingAmount)} $coin',
|
|
|
|
|
|
|
+ label: l10n.stakingLockDaysLabel,
|
|
|
|
|
+ value: _lockDaysText(),
|
|
|
),
|
|
),
|
|
|
_StakingOrderRow(
|
|
_StakingOrderRow(
|
|
|
- label: l10n.startTime,
|
|
|
|
|
- value: order.displayStartTime,
|
|
|
|
|
|
|
+ label: l10n.assetsReleased,
|
|
|
|
|
+ value:
|
|
|
|
|
+ '${_fmtReleasedCompact(released)}/${_fmtReleasedCompact(amount)}',
|
|
|
),
|
|
),
|
|
|
- _StakingOrderRow(
|
|
|
|
|
- label: l10n.statusLabel,
|
|
|
|
|
- value: _statusLabel(l10n),
|
|
|
|
|
- valueColor: _statusColor(),
|
|
|
|
|
|
|
+ const SizedBox(height: 8),
|
|
|
|
|
+ ClipRRect(
|
|
|
|
|
+ borderRadius: BorderRadius.circular(999),
|
|
|
|
|
+ child: SizedBox(
|
|
|
|
|
+ height: 8,
|
|
|
|
|
+ child: Stack(
|
|
|
|
|
+ fit: StackFit.expand,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Container(color: const Color(0xFF12161C)),
|
|
|
|
|
+ FractionallySizedBox(
|
|
|
|
|
+ alignment: Alignment.centerLeft,
|
|
|
|
|
+ widthFactor: progress,
|
|
|
|
|
+ child: Container(color: _progressColor()),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
),
|
|
),
|
|
|
],
|
|
],
|
|
|
),
|
|
),
|
|
@@ -390,30 +443,28 @@ class _StakingOrderRow extends StatelessWidget {
|
|
|
const _StakingOrderRow({
|
|
const _StakingOrderRow({
|
|
|
required this.label,
|
|
required this.label,
|
|
|
required this.value,
|
|
required this.value,
|
|
|
- this.valueColor,
|
|
|
|
|
});
|
|
});
|
|
|
final String label;
|
|
final String label;
|
|
|
final String value;
|
|
final String value;
|
|
|
- final Color? valueColor;
|
|
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
|
final cs = Theme.of(context).colorScheme;
|
|
final cs = Theme.of(context).colorScheme;
|
|
|
return Padding(
|
|
return Padding(
|
|
|
- padding: const EdgeInsets.only(bottom: 8),
|
|
|
|
|
|
|
+ padding: const EdgeInsets.only(bottom: 10),
|
|
|
child: Row(
|
|
child: Row(
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
children: [
|
|
children: [
|
|
|
Text(
|
|
Text(
|
|
|
label,
|
|
label,
|
|
|
- style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13),
|
|
|
|
|
|
|
+ style: TextStyle(color: cs.onSurface.withAlpha(140), fontSize: 13),
|
|
|
),
|
|
),
|
|
|
- Flexible(
|
|
|
|
|
|
|
+ Expanded(
|
|
|
child: Text(
|
|
child: Text(
|
|
|
value,
|
|
value,
|
|
|
textAlign: TextAlign.right,
|
|
textAlign: TextAlign.right,
|
|
|
style: TextStyle(
|
|
style: TextStyle(
|
|
|
- color: valueColor ?? cs.onSurface,
|
|
|
|
|
|
|
+ color: cs.onSurface,
|
|
|
fontSize: 13,
|
|
fontSize: 13,
|
|
|
fontWeight: FontWeight.w500,
|
|
fontWeight: FontWeight.w500,
|
|
|
),
|
|
),
|