ido_overview_cards.dart 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:go_router/go_router.dart';
  4. import 'package:intl/intl.dart';
  5. import '../../../core/l10n/app_localizations.dart';
  6. import '../../../core/theme/app_colors.dart';
  7. import '../../../providers/asset_provider.dart';
  8. import '../../../providers/futures_provider.dart' show activeBottomTabProvider;
  9. /// 资产页锁仓账户子 Tab 索引
  10. const int kAssetStakingSubTabIndex = 5;
  11. /// IDO 预售页「我的团队 / 锁仓总量」入口卡片(对齐 Web ido-overview)
  12. class IdoOverviewCards extends ConsumerWidget {
  13. const IdoOverviewCards({
  14. super.key,
  15. required this.teamNum,
  16. required this.lockedTotal,
  17. this.loading = false,
  18. });
  19. final int teamNum;
  20. final String lockedTotal;
  21. final bool loading;
  22. static String _fmtAmount(String value, {int maxFrac = 8}) {
  23. final parsed = double.tryParse(value);
  24. if (parsed == null || !parsed.isFinite) {
  25. return '0';
  26. }
  27. if (parsed == 0) {
  28. return '0';
  29. }
  30. return NumberFormat('#,##0.${'#' * maxFrac}', 'en_US').format(parsed);
  31. }
  32. void _goToStakingAccount(BuildContext context, WidgetRef ref) {
  33. ref.read(currentAssetSubTabProvider.notifier).state =
  34. kAssetStakingSubTabIndex;
  35. ref.read(activeBottomTabProvider.notifier).state = kAssetStakingSubTabIndex;
  36. context.go('/asset?tab=staking');
  37. }
  38. @override
  39. Widget build(BuildContext context, WidgetRef ref) {
  40. final l10n = AppLocalizations.of(context)!;
  41. final isDark = Theme.of(context).brightness == Brightness.dark;
  42. final primary = isDark ? Colors.white : Theme.of(context).colorScheme.onSurface;
  43. final labelColor = primary.withAlpha(140);
  44. final cardBg = primary.withAlpha(10);
  45. final cardBorder = primary.withAlpha(26);
  46. return Padding(
  47. padding: const EdgeInsets.fromLTRB(16, 0, 16, 24),
  48. child: IntrinsicHeight(
  49. child: Row(
  50. crossAxisAlignment: CrossAxisAlignment.stretch,
  51. children: [
  52. Expanded(
  53. child: _OverviewCard(
  54. label: l10n.financeMyTeam,
  55. value: loading ? '—' : _fmtAmount('$teamNum', maxFrac: 0),
  56. labelColor: labelColor,
  57. valueColor: primary,
  58. backgroundColor: cardBg,
  59. borderColor: cardBorder,
  60. onTap: () => context.push('/finance/team'),
  61. ),
  62. ),
  63. const SizedBox(width: 12),
  64. Expanded(
  65. child: _OverviewCard(
  66. label: l10n.assetsStakingTotal,
  67. value: loading ? '—' : _fmtAmount(lockedTotal, maxFrac: 2),
  68. labelColor: labelColor,
  69. valueColor: primary,
  70. backgroundColor: cardBg,
  71. borderColor: cardBorder,
  72. onTap: () => _goToStakingAccount(context, ref),
  73. ),
  74. ),
  75. ],
  76. ),
  77. ),
  78. );
  79. }
  80. }
  81. class _OverviewCard extends StatelessWidget {
  82. const _OverviewCard({
  83. required this.label,
  84. required this.value,
  85. required this.labelColor,
  86. required this.valueColor,
  87. required this.backgroundColor,
  88. required this.borderColor,
  89. required this.onTap,
  90. });
  91. final String label;
  92. final String value;
  93. final Color labelColor;
  94. final Color valueColor;
  95. final Color backgroundColor;
  96. final Color borderColor;
  97. final VoidCallback onTap;
  98. @override
  99. Widget build(BuildContext context) {
  100. return Material(
  101. color: backgroundColor,
  102. shape: RoundedRectangleBorder(
  103. borderRadius: BorderRadius.circular(16),
  104. side: BorderSide(color: borderColor),
  105. ),
  106. child: InkWell(
  107. onTap: onTap,
  108. borderRadius: BorderRadius.circular(16),
  109. splashColor: AppColors.brand.withAlpha(40),
  110. highlightColor: AppColors.brand.withAlpha(20),
  111. child: Padding(
  112. padding: const EdgeInsets.fromLTRB(20, 20, 20, 22),
  113. child: Column(
  114. crossAxisAlignment: CrossAxisAlignment.start,
  115. children: [
  116. Row(
  117. children: [
  118. Flexible(
  119. child: Text(
  120. label,
  121. style: TextStyle(color: labelColor, fontSize: 14),
  122. maxLines: 1,
  123. overflow: TextOverflow.ellipsis,
  124. ),
  125. ),
  126. Text(
  127. ' >',
  128. style: TextStyle(
  129. color: labelColor.withAlpha(180),
  130. fontSize: 13,
  131. ),
  132. ),
  133. ],
  134. ),
  135. const SizedBox(height: 12),
  136. SizedBox(
  137. height: 26,
  138. width: double.infinity,
  139. child: Align(
  140. alignment: Alignment.centerLeft,
  141. child: FittedBox(
  142. fit: BoxFit.scaleDown,
  143. alignment: Alignment.centerLeft,
  144. child: Text(
  145. value,
  146. maxLines: 1,
  147. style: TextStyle(
  148. color: valueColor,
  149. fontSize: 22,
  150. fontWeight: FontWeight.w600,
  151. height: 1.1,
  152. fontFeatures: const [FontFeature.tabularFigures()],
  153. ),
  154. ),
  155. ),
  156. ),
  157. ),
  158. ],
  159. ),
  160. ),
  161. ),
  162. );
  163. }
  164. }