airdrop_screen.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter_riverpod/flutter_riverpod.dart';
  4. import 'package:go_router/go_router.dart';
  5. import '../../../core/l10n/app_localizations.dart';
  6. import '../../../core/theme/app_colors.dart';
  7. import '../../../core/utils/top_toast.dart';
  8. import '../../../data/models/finance/airdrop_eligibility.dart';
  9. import '../../../data/models/finance/airdrop_record_item.dart';
  10. import '../../../providers/auth_provider.dart';
  11. import '../../../providers/staking_provider.dart';
  12. bool _airdropIsDark(BuildContext context) =>
  13. Theme.of(context).brightness == Brightness.dark;
  14. Color _airdropPageBg(BuildContext context) => _airdropIsDark(context)
  15. ? const Color(0xFF010A14)
  16. : Theme.of(context).colorScheme.surface;
  17. Color _airdropCardBg(BuildContext context) => _airdropIsDark(context)
  18. ? const Color(0xFF11181D)
  19. : Theme.of(context).colorScheme.surfaceContainerHighest;
  20. Color _airdropPrimaryText(BuildContext context) => _airdropIsDark(context)
  21. ? Colors.white
  22. : Theme.of(context).colorScheme.onSurface;
  23. Color _airdropHintText(BuildContext context) =>
  24. _airdropPrimaryText(context).withAlpha(150);
  25. class AirdropScreen extends ConsumerStatefulWidget {
  26. const AirdropScreen({
  27. super.key,
  28. this.showAppBar = true,
  29. });
  30. final bool showAppBar;
  31. @override
  32. ConsumerState<AirdropScreen> createState() => _AirdropScreenState();
  33. }
  34. class _AirdropScreenState extends ConsumerState<AirdropScreen> {
  35. void _log(String message) {
  36. if (kDebugMode) {
  37. debugPrint('[Airdrop][UI] $message');
  38. }
  39. }
  40. @override
  41. void initState() {
  42. super.initState();
  43. // 避免在 build 阶段触发 provider 更新,导致布局重入断言。
  44. WidgetsBinding.instance.addPostFrameCallback((_) {
  45. if (mounted) {
  46. ref.read(airdropProvider.notifier).init();
  47. }
  48. });
  49. }
  50. @override
  51. Widget build(BuildContext context) {
  52. final l10n = AppLocalizations.of(context);
  53. final state = ref.watch(airdropProvider);
  54. final isLoggedIn = ref.watch(isLoggedInProvider);
  55. final eligibility = state.eligibility;
  56. final canClaim = isLoggedIn && eligibility.eligible && !state.isClaiming;
  57. final itemCount = _itemCount(state, isLoggedIn);
  58. _log(
  59. 'build isLoggedIn=$isLoggedIn eligLoading=${state.isLoadingEligibility} recordsLoading=${state.isLoadingRecords} records=${state.records.length} pageNo=${state.pageNo}/${state.totalPages} itemCount=$itemCount error=${state.errorMessage}',
  60. );
  61. final body = _buildBody(
  62. l10n: l10n,
  63. state: state,
  64. isLoggedIn: isLoggedIn,
  65. eligibility: eligibility,
  66. canClaim: canClaim,
  67. );
  68. if (!widget.showAppBar) {
  69. return ColoredBox(
  70. color: _airdropPageBg(context),
  71. child: body,
  72. );
  73. }
  74. return Scaffold(
  75. backgroundColor: _airdropPageBg(context),
  76. appBar: AppBar(
  77. backgroundColor: Colors.transparent,
  78. foregroundColor: _airdropPrimaryText(context),
  79. elevation: 0,
  80. centerTitle: true,
  81. title: Text(
  82. l10n?.airdropTitle ?? '',
  83. style: TextStyle(color: _airdropPrimaryText(context)),
  84. ),
  85. ),
  86. body: body,
  87. );
  88. }
  89. Future<void> _onRefresh() async {
  90. await ref.read(airdropProvider.notifier).init();
  91. }
  92. Widget _buildBody({
  93. required AppLocalizations? l10n,
  94. required AirdropState state,
  95. required bool isLoggedIn,
  96. required AirdropEligibility eligibility,
  97. required bool canClaim,
  98. }) {
  99. if (l10n == null) {
  100. return const Center(child: CircularProgressIndicator());
  101. }
  102. final itemCount = _itemCount(state, isLoggedIn);
  103. return RefreshIndicator(
  104. onRefresh: _onRefresh,
  105. color: AppColors.brand,
  106. child: ListView.builder(
  107. physics: const AlwaysScrollableScrollPhysics(),
  108. padding: const EdgeInsets.fromLTRB(15, 10, 15, 24),
  109. itemCount: itemCount,
  110. itemBuilder: (context, index) {
  111. return _itemBuilder(
  112. context: context,
  113. index: index,
  114. l10n: l10n,
  115. state: state,
  116. isLoggedIn: isLoggedIn,
  117. eligibility: eligibility,
  118. canClaim: canClaim,
  119. );
  120. },
  121. ),
  122. );
  123. }
  124. // ── 计算列表项总数 ──────────────────────────────────────────
  125. int _itemCount(AirdropState state, bool isLoggedIn) {
  126. int n = 0;
  127. n++; // hero
  128. if (state.errorMessage != null && state.errorMessage!.isNotEmpty) {
  129. n++; // error
  130. }
  131. n += 2; // section spacer + section title
  132. n += 2; // invite spacer + invite task
  133. n += 2; // staking spacer + staking task
  134. n += 2; // claimable spacer + claimable card
  135. n += 2; // claim button spacer + claim button
  136. if (!isLoggedIn) {
  137. n++; // login hint
  138. } else {
  139. n += 2; // records spacer + records title
  140. if (state.isLoadingRecords && state.records.isEmpty) {
  141. n++; // loading
  142. } else if (state.records.isEmpty) {
  143. n++; // empty
  144. } else {
  145. n += state.records.length; // record items
  146. }
  147. if (state.hasMore) n++; // load more
  148. }
  149. return n;
  150. }
  151. // ── 按 index 构建组件 ──────────────────────────────────────
  152. Widget _itemBuilder({
  153. required BuildContext context,
  154. required int index,
  155. required AppLocalizations l10n,
  156. required AirdropState state,
  157. required bool isLoggedIn,
  158. required AirdropEligibility eligibility,
  159. required bool canClaim,
  160. }) {
  161. int i = 0;
  162. final total = _itemCount(state, isLoggedIn);
  163. if (index < 0 || index >= total) {
  164. _log('itemBuilder out of range index=$index total=$total');
  165. _log(
  166. 'itemBuilder fallback index=$index total=$total records=${state.records.length} hasMore=${state.hasMore}',
  167. );
  168. return const SizedBox.shrink();
  169. }
  170. try {
  171. // 0: hero
  172. if (index == i++) {
  173. return _HeroPanel(
  174. title: l10n.airdropTitle,
  175. subtitle: eligibility.message.isNotEmpty
  176. ? eligibility.message
  177. : l10n.airdropHasPendingReward,
  178. loading: state.isLoadingEligibility,
  179. );
  180. }
  181. // 1: error (conditional)
  182. if (state.errorMessage != null && state.errorMessage!.isNotEmpty) {
  183. if (index == i++) {
  184. return _ErrorCard(message: state.errorMessage!);
  185. }
  186. }
  187. // section title
  188. if (index == i++) return const SizedBox(height: 20);
  189. if (index == i++) {
  190. return _SectionTitle(title: l10n.airdropApplyTitle);
  191. }
  192. // invite task
  193. if (index == i++) return const SizedBox(height: 12);
  194. if (index == i++) {
  195. return _TaskCard(
  196. title: l10n.airdropInviteRequirement(
  197. '${eligibility.inviteCount}',
  198. '${eligibility.requiredInviteCount}',
  199. ),
  200. subtitle: eligibility.inviteTaskCompleted
  201. ? l10n.completed
  202. : '${eligibility.inviteCount}/${eligibility.requiredInviteCount}',
  203. done: eligibility.inviteTaskCompleted,
  204. btnText: l10n.inviteFriends,
  205. onTap: eligibility.inviteTaskCompleted
  206. ? null
  207. : () => context.push('/user/referral'),
  208. );
  209. }
  210. // staking task
  211. if (index == i++) return const SizedBox(height: 10);
  212. if (index == i++) {
  213. return _TaskCard(
  214. title: l10n.airdropHasActiveStaking,
  215. subtitle: eligibility.hasActiveStaking ? l10n.completed : l10n.noData,
  216. done: eligibility.hasActiveStaking,
  217. btnText: l10n.stakingTitle,
  218. onTap: () => context.push('/finance/ido'),
  219. );
  220. }
  221. // claimable card
  222. if (index == i++) return const SizedBox(height: 12);
  223. if (index == i++) {
  224. return _ClaimablePanel(
  225. label: l10n.airdropClaimable,
  226. amount: _fmtAmount(eligibility),
  227. coinUnit: eligibility.claimableCoinUnit ?? 'IBIT',
  228. );
  229. }
  230. // claim button
  231. if (index == i++) return const SizedBox(height: 16);
  232. if (index == i++) {
  233. return SizedBox(
  234. height: 50,
  235. child: ElevatedButton(
  236. onPressed: canClaim ? () => _onClaim(l10n, eligibility) : null,
  237. style: ElevatedButton.styleFrom(
  238. backgroundColor: AppColors.brand,
  239. foregroundColor: Colors.black,
  240. disabledBackgroundColor: _airdropCardBg(context),
  241. disabledForegroundColor: const Color(0xFF757575),
  242. shape: RoundedRectangleBorder(
  243. borderRadius: BorderRadius.circular(12)),
  244. ),
  245. child: state.isClaiming
  246. ? const SizedBox(
  247. width: 18,
  248. height: 18,
  249. child: CircularProgressIndicator(strokeWidth: 2))
  250. : Text(
  251. canClaim
  252. ? l10n.airdropClaimNow
  253. : l10n.airdropClaimAfterTasks,
  254. ),
  255. ),
  256. );
  257. }
  258. // not logged in hint
  259. if (!isLoggedIn) {
  260. if (index == i++) {
  261. return Padding(
  262. padding: const EdgeInsets.only(top: 12),
  263. child: Center(
  264. child: Text(l10n.airdropNotEligible,
  265. style: TextStyle(
  266. color: _airdropHintText(context), fontSize: 12)),
  267. ),
  268. );
  269. }
  270. return const SizedBox.shrink();
  271. }
  272. // records section
  273. if (index == i++) return const SizedBox(height: 20);
  274. if (index == i++) {
  275. return Text(
  276. l10n.airdropRecords,
  277. style: TextStyle(
  278. color: _airdropPrimaryText(context),
  279. fontSize: 16,
  280. fontWeight: FontWeight.w600),
  281. );
  282. }
  283. if (state.isLoadingRecords && state.records.isEmpty) {
  284. if (index == i++) {
  285. return const Padding(
  286. padding: EdgeInsets.symmetric(vertical: 24),
  287. child: Center(
  288. child: SizedBox(
  289. width: 24,
  290. height: 24,
  291. child: CircularProgressIndicator(strokeWidth: 2))),
  292. );
  293. }
  294. } else if (state.records.isEmpty) {
  295. if (index == i++) {
  296. return Container(
  297. padding: const EdgeInsets.symmetric(vertical: 20),
  298. alignment: Alignment.center,
  299. decoration: BoxDecoration(
  300. color: _airdropCardBg(context),
  301. borderRadius: BorderRadius.circular(10)),
  302. child: Text(l10n.noData,
  303. style: TextStyle(color: _airdropHintText(context))),
  304. );
  305. }
  306. } else {
  307. final ri = index - i;
  308. if (ri >= 0 && ri < state.records.length) {
  309. return _RecordCard(record: state.records[ri]);
  310. }
  311. i += state.records.length;
  312. if (state.hasMore) {
  313. if (index == i++) {
  314. return TextButton(
  315. onPressed: state.isLoadingMore
  316. ? null
  317. : () => ref.read(airdropProvider.notifier).loadMore(),
  318. child: Text(state.isLoadingMore ? l10n.loading : l10n.viewMore,
  319. style: const TextStyle(color: AppColors.brand)),
  320. );
  321. }
  322. }
  323. }
  324. return const SizedBox.shrink();
  325. } catch (e, st) {
  326. _log(
  327. 'itemBuilder exception index=$index total=$total records=${state.records.length} error=$e',
  328. );
  329. _log('$st');
  330. return Container(
  331. margin: const EdgeInsets.only(top: 8),
  332. padding: const EdgeInsets.all(12),
  333. decoration: BoxDecoration(
  334. color: const Color(0x33FF5252),
  335. borderRadius: BorderRadius.circular(8),
  336. border: Border.all(color: const Color(0x66FF5252)),
  337. ),
  338. child: Text(
  339. 'UI render error @index=$index: $e',
  340. style: const TextStyle(color: Color(0xFFFF8A80), fontSize: 12),
  341. ),
  342. );
  343. }
  344. }
  345. // ── 格式化金额 ──────────────────────────────────────────────
  346. String _fmtAmount(AirdropEligibility e) {
  347. final d = double.tryParse(e.claimableAmount) ?? 0;
  348. if (d == 0) return '0';
  349. final s = d.toStringAsFixed(8);
  350. // 去掉尾部多余的 0
  351. final trimmed = s.replaceAll(RegExp(r'0+$'), '');
  352. return trimmed.endsWith('.') ? '${trimmed}0' : trimmed;
  353. }
  354. // ── 领取逻辑 ────────────────────────────────────────────────
  355. Future<void> _onClaim(
  356. AppLocalizations l10n, AirdropEligibility eligibility) async {
  357. if (!eligibility.eligible) {
  358. showTopToast(context, message: l10n.airdropNotEligible);
  359. return;
  360. }
  361. final err = await ref.read(airdropProvider.notifier).claim();
  362. if (!mounted) return;
  363. if (err == null) {
  364. await showDialog(
  365. context: context,
  366. builder: (ctx) => AlertDialog(
  367. title: Text(l10n.tips),
  368. content: Text(l10n.airdropClaimSuccess),
  369. actions: [
  370. TextButton(
  371. onPressed: () => Navigator.of(ctx).pop(),
  372. child: Text(l10n.confirm)),
  373. ],
  374. ),
  375. );
  376. } else {
  377. showTopToast(context, message: err);
  378. }
  379. }
  380. }
  381. // ═══════════════════════════════════════════════════════════════
  382. // 子组件
  383. // ═══════════════════════════════════════════════════════════════
  384. // ── 英雄区(文字左 + 图片右)──────────────────────────────────
  385. class _HeroPanel extends StatelessWidget {
  386. const _HeroPanel(
  387. {required this.title, required this.subtitle, this.loading = false});
  388. final String title;
  389. final String subtitle;
  390. final bool loading;
  391. @override
  392. Widget build(BuildContext context) {
  393. return Padding(
  394. padding: const EdgeInsets.only(bottom: 8),
  395. child: Row(
  396. crossAxisAlignment: CrossAxisAlignment.center,
  397. children: [
  398. Expanded(
  399. child: Column(
  400. crossAxisAlignment: CrossAxisAlignment.start,
  401. mainAxisSize: MainAxisSize.min,
  402. children: [
  403. Text(title,
  404. style: TextStyle(
  405. color: _airdropPrimaryText(context),
  406. fontSize: 26,
  407. fontWeight: FontWeight.w700)),
  408. const SizedBox(height: 8),
  409. if (loading)
  410. const SizedBox(
  411. width: 18,
  412. height: 18,
  413. child: CircularProgressIndicator(strokeWidth: 2))
  414. else
  415. Text(subtitle,
  416. maxLines: 5,
  417. overflow: TextOverflow.ellipsis,
  418. style: TextStyle(
  419. color: _airdropHintText(context),
  420. fontSize: 10,
  421. height: 1.45)),
  422. ],
  423. ),
  424. ),
  425. const SizedBox(width: 16),
  426. Image.asset(
  427. 'assets/images/finance/airdrop_hero.png',
  428. width: 126,
  429. height: 126,
  430. fit: BoxFit.contain,
  431. errorBuilder: (_, __, ___) => Container(
  432. width: 126,
  433. height: 126,
  434. alignment: Alignment.center,
  435. decoration: BoxDecoration(
  436. color: _airdropCardBg(context),
  437. borderRadius: BorderRadius.circular(12),
  438. ),
  439. child: const Icon(Icons.card_giftcard_outlined,
  440. color: AppColors.brand, size: 48),
  441. ),
  442. ),
  443. ],
  444. ),
  445. );
  446. }
  447. }
  448. // ── 错误卡片 ──────────────────────────────────────────────────
  449. class _ErrorCard extends StatelessWidget {
  450. const _ErrorCard({required this.message});
  451. final String message;
  452. @override
  453. Widget build(BuildContext context) {
  454. return Container(
  455. margin: const EdgeInsets.only(bottom: 8),
  456. padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
  457. decoration: BoxDecoration(
  458. color: const Color(0x33FF5252),
  459. borderRadius: BorderRadius.circular(8),
  460. border: Border.all(color: const Color(0x66FF5252)),
  461. ),
  462. child: Text(message,
  463. style: const TextStyle(color: Color(0xFFFF8A80), fontSize: 12)),
  464. );
  465. }
  466. }
  467. // ── 区块标题 ──────────────────────────────────────────────────
  468. class _SectionTitle extends StatelessWidget {
  469. const _SectionTitle({required this.title});
  470. final String title;
  471. @override
  472. Widget build(BuildContext context) {
  473. return Center(
  474. child: Row(
  475. mainAxisSize: MainAxisSize.min,
  476. crossAxisAlignment: CrossAxisAlignment.center,
  477. mainAxisAlignment: MainAxisAlignment.center,
  478. children: [
  479. // Container(
  480. // width: 14,
  481. // height: 3,
  482. // decoration: BoxDecoration(
  483. // color: AppColors.brand,
  484. // borderRadius: BorderRadius.circular(2))),
  485. Image.asset(
  486. "assets/images/ico_airdrop_left.png",
  487. height: 10,
  488. ),
  489. const SizedBox(width: 5),
  490. Padding(
  491. padding: EdgeInsetsGeometry.only(bottom: 3),
  492. child: Text(title,
  493. style: TextStyle(
  494. color: _airdropPrimaryText(context),
  495. fontSize: 14,
  496. fontWeight: FontWeight.w600)),
  497. ),
  498. const SizedBox(width: 5),
  499. Image.asset(
  500. "assets/images/ico_airdrop_right.png",
  501. height: 10,
  502. ),
  503. // Container(
  504. // width: 14,
  505. // height: 3,
  506. // decoration: BoxDecoration(
  507. // color: AppColors.brand,
  508. // borderRadius: BorderRadius.circular(2))),
  509. ],
  510. ),
  511. );
  512. }
  513. }
  514. // ── 任务卡片 ──────────────────────────────────────────────────
  515. class _TaskCard extends StatelessWidget {
  516. const _TaskCard({
  517. required this.title,
  518. required this.subtitle,
  519. required this.done,
  520. required this.btnText,
  521. this.onTap,
  522. });
  523. final String title;
  524. final String subtitle;
  525. final bool done;
  526. final String btnText;
  527. final VoidCallback? onTap;
  528. @override
  529. Widget build(BuildContext context) {
  530. return Container(
  531. padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
  532. decoration: BoxDecoration(
  533. color: _airdropCardBg(context),
  534. borderRadius: BorderRadius.circular(8)),
  535. child: Row(
  536. children: [
  537. Expanded(
  538. child: Column(
  539. crossAxisAlignment: CrossAxisAlignment.start,
  540. mainAxisSize: MainAxisSize.min,
  541. children: [
  542. Text(title,
  543. style: TextStyle(
  544. color: _airdropPrimaryText(context), fontSize: 13)),
  545. const SizedBox(height: 6),
  546. Text(
  547. done ? '$subtitle ✅' : subtitle,
  548. style:
  549. TextStyle(color: _airdropHintText(context), fontSize: 11),
  550. ),
  551. ],
  552. ),
  553. ),
  554. const SizedBox(width: 12),
  555. SizedBox(
  556. height: 30,
  557. child: Material(
  558. color: onTap == null
  559. ? AppColors.brand.withAlpha(128)
  560. : AppColors.brand,
  561. borderRadius: BorderRadius.circular(6),
  562. child: InkWell(
  563. onTap: onTap,
  564. borderRadius: BorderRadius.circular(6),
  565. child: Padding(
  566. padding: const EdgeInsets.symmetric(horizontal: 12),
  567. child: Center(
  568. child: Text(
  569. btnText,
  570. style: TextStyle(
  571. fontSize: 12,
  572. color: onTap == null
  573. ? Colors.black.withAlpha(128)
  574. : Colors.black,
  575. fontWeight: FontWeight.w500,
  576. ),
  577. ),
  578. ),
  579. ),
  580. ),
  581. ),
  582. ),
  583. ],
  584. ),
  585. );
  586. }
  587. }
  588. // ── 可领取金额面板 ────────────────────────────────────────────
  589. class _ClaimablePanel extends StatelessWidget {
  590. const _ClaimablePanel(
  591. {required this.label, required this.amount, required this.coinUnit});
  592. final String label;
  593. final String amount;
  594. final String coinUnit;
  595. @override
  596. Widget build(BuildContext context) {
  597. return Container(
  598. padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
  599. decoration: BoxDecoration(
  600. color: _airdropCardBg(context),
  601. borderRadius: BorderRadius.circular(8)),
  602. child: Row(
  603. children: [
  604. Expanded(
  605. child: Text(label,
  606. style:
  607. TextStyle(color: _airdropHintText(context), fontSize: 12)),
  608. ),
  609. Text('$amount $coinUnit',
  610. style: TextStyle(
  611. color: _airdropPrimaryText(context),
  612. fontSize: 18,
  613. fontWeight: FontWeight.w700)),
  614. ],
  615. ),
  616. );
  617. }
  618. }
  619. // ── 领取记录条目 ──────────────────────────────────────────────
  620. class _RecordCard extends StatelessWidget {
  621. const _RecordCard({required this.record});
  622. final AirdropRecordItem record;
  623. @override
  624. Widget build(BuildContext context) {
  625. final l10n = AppLocalizations.of(context);
  626. final statusText = switch (record.status) {
  627. 0 => l10n?.airdropStatusPending ?? 'Pending',
  628. 1 => l10n?.airdropStatusGranted ?? 'Granted',
  629. 2 => l10n?.airdropStatusReviewing ?? 'Reviewing',
  630. 3 => l10n?.airdropStatusRejected ?? 'Rejected',
  631. _ => '${record.status}',
  632. };
  633. return Container(
  634. margin: const EdgeInsets.only(bottom: 8),
  635. padding: const EdgeInsets.all(12),
  636. decoration: BoxDecoration(
  637. color: _airdropCardBg(context),
  638. borderRadius: BorderRadius.circular(8)),
  639. child: Row(
  640. children: [
  641. Expanded(
  642. child: Column(
  643. crossAxisAlignment: CrossAxisAlignment.start,
  644. mainAxisSize: MainAxisSize.min,
  645. children: [
  646. Text(
  647. '${record.amount} ${record.coinUnit ?? ''}',
  648. style: TextStyle(
  649. fontWeight: FontWeight.w600,
  650. color: _airdropPrimaryText(context)),
  651. ),
  652. const SizedBox(height: 4),
  653. Text(
  654. record.createTime ?? '--',
  655. style:
  656. TextStyle(color: _airdropHintText(context), fontSize: 12),
  657. ),
  658. ],
  659. ),
  660. ),
  661. Text(statusText, style: TextStyle(color: _airdropHintText(context))),
  662. ],
  663. ),
  664. );
  665. }
  666. }