airdrop_screen.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  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(l10n.airdropClaimNow),
  251. ),
  252. );
  253. }
  254. // not logged in hint
  255. if (!isLoggedIn) {
  256. if (index == i++) {
  257. return Padding(
  258. padding: const EdgeInsets.only(top: 12),
  259. child: Center(
  260. child: Text(l10n.airdropNotEligible,
  261. style: TextStyle(
  262. color: _airdropHintText(context), fontSize: 12)),
  263. ),
  264. );
  265. }
  266. return const SizedBox.shrink();
  267. }
  268. // records section
  269. if (index == i++) return const SizedBox(height: 20);
  270. if (index == i++) {
  271. return Text(
  272. l10n.airdropRecords,
  273. style: TextStyle(
  274. color: _airdropPrimaryText(context),
  275. fontSize: 16,
  276. fontWeight: FontWeight.w600),
  277. );
  278. }
  279. if (state.isLoadingRecords && state.records.isEmpty) {
  280. if (index == i++) {
  281. return const Padding(
  282. padding: EdgeInsets.symmetric(vertical: 24),
  283. child: Center(
  284. child: SizedBox(
  285. width: 24,
  286. height: 24,
  287. child: CircularProgressIndicator(strokeWidth: 2))),
  288. );
  289. }
  290. } else if (state.records.isEmpty) {
  291. if (index == i++) {
  292. return Container(
  293. padding: const EdgeInsets.symmetric(vertical: 20),
  294. alignment: Alignment.center,
  295. decoration: BoxDecoration(
  296. color: _airdropCardBg(context),
  297. borderRadius: BorderRadius.circular(10)),
  298. child: Text(l10n.noData,
  299. style: TextStyle(color: _airdropHintText(context))),
  300. );
  301. }
  302. } else {
  303. final ri = index - i;
  304. if (ri >= 0 && ri < state.records.length) {
  305. return _RecordCard(record: state.records[ri]);
  306. }
  307. i += state.records.length;
  308. if (state.hasMore) {
  309. if (index == i++) {
  310. return TextButton(
  311. onPressed: state.isLoadingMore
  312. ? null
  313. : () => ref.read(airdropProvider.notifier).loadMore(),
  314. child: Text(state.isLoadingMore ? l10n.loading : l10n.viewMore,
  315. style: const TextStyle(color: AppColors.brand)),
  316. );
  317. }
  318. }
  319. }
  320. return const SizedBox.shrink();
  321. } catch (e, st) {
  322. _log(
  323. 'itemBuilder exception index=$index total=$total records=${state.records.length} error=$e',
  324. );
  325. _log('$st');
  326. return Container(
  327. margin: const EdgeInsets.only(top: 8),
  328. padding: const EdgeInsets.all(12),
  329. decoration: BoxDecoration(
  330. color: const Color(0x33FF5252),
  331. borderRadius: BorderRadius.circular(8),
  332. border: Border.all(color: const Color(0x66FF5252)),
  333. ),
  334. child: Text(
  335. 'UI render error @index=$index: $e',
  336. style: const TextStyle(color: Color(0xFFFF8A80), fontSize: 12),
  337. ),
  338. );
  339. }
  340. }
  341. // ── 格式化金额 ──────────────────────────────────────────────
  342. String _fmtAmount(AirdropEligibility e) {
  343. final d = double.tryParse(e.claimableAmount) ?? 0;
  344. if (d == 0) return '0';
  345. final s = d.toStringAsFixed(8);
  346. // 去掉尾部多余的 0
  347. final trimmed = s.replaceAll(RegExp(r'0+$'), '');
  348. return trimmed.endsWith('.') ? '${trimmed}0' : trimmed;
  349. }
  350. // ── 领取逻辑 ────────────────────────────────────────────────
  351. Future<void> _onClaim(
  352. AppLocalizations l10n, AirdropEligibility eligibility) async {
  353. if (!eligibility.eligible) {
  354. showTopToast(context, message: l10n.airdropNotEligible);
  355. return;
  356. }
  357. final err = await ref.read(airdropProvider.notifier).claim();
  358. if (!mounted) return;
  359. if (err == null) {
  360. await showDialog(
  361. context: context,
  362. builder: (ctx) => AlertDialog(
  363. title: Text(l10n.tips),
  364. content: Text(l10n.airdropClaimSuccess),
  365. actions: [
  366. TextButton(
  367. onPressed: () => Navigator.of(ctx).pop(),
  368. child: Text(l10n.confirm)),
  369. ],
  370. ),
  371. );
  372. } else {
  373. showTopToast(context, message: err);
  374. }
  375. }
  376. }
  377. // ═══════════════════════════════════════════════════════════════
  378. // 子组件
  379. // ═══════════════════════════════════════════════════════════════
  380. // ── 英雄区(文字左 + 图片右)──────────────────────────────────
  381. class _HeroPanel extends StatelessWidget {
  382. const _HeroPanel(
  383. {required this.title, required this.subtitle, this.loading = false});
  384. final String title;
  385. final String subtitle;
  386. final bool loading;
  387. @override
  388. Widget build(BuildContext context) {
  389. return Padding(
  390. padding: const EdgeInsets.only(bottom: 8),
  391. child: Row(
  392. crossAxisAlignment: CrossAxisAlignment.center,
  393. children: [
  394. Expanded(
  395. child: Column(
  396. crossAxisAlignment: CrossAxisAlignment.start,
  397. mainAxisSize: MainAxisSize.min,
  398. children: [
  399. Text(title,
  400. style: TextStyle(
  401. color: _airdropPrimaryText(context),
  402. fontSize: 26,
  403. fontWeight: FontWeight.w700)),
  404. const SizedBox(height: 8),
  405. if (loading)
  406. const SizedBox(
  407. width: 18,
  408. height: 18,
  409. child: CircularProgressIndicator(strokeWidth: 2))
  410. else
  411. Text(subtitle,
  412. maxLines: 5,
  413. overflow: TextOverflow.ellipsis,
  414. style: TextStyle(
  415. color: _airdropHintText(context),
  416. fontSize: 10,
  417. height: 1.45)),
  418. ],
  419. ),
  420. ),
  421. const SizedBox(width: 16),
  422. Image.asset(
  423. 'assets/images/finance/airdrop_hero.png',
  424. width: 126,
  425. height: 126,
  426. fit: BoxFit.contain,
  427. errorBuilder: (_, __, ___) => Container(
  428. width: 126,
  429. height: 126,
  430. alignment: Alignment.center,
  431. decoration: BoxDecoration(
  432. color: _airdropCardBg(context),
  433. borderRadius: BorderRadius.circular(12),
  434. ),
  435. child: const Icon(Icons.card_giftcard_outlined,
  436. color: AppColors.brand, size: 48),
  437. ),
  438. ),
  439. ],
  440. ),
  441. );
  442. }
  443. }
  444. // ── 错误卡片 ──────────────────────────────────────────────────
  445. class _ErrorCard extends StatelessWidget {
  446. const _ErrorCard({required this.message});
  447. final String message;
  448. @override
  449. Widget build(BuildContext context) {
  450. return Container(
  451. margin: const EdgeInsets.only(bottom: 8),
  452. padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
  453. decoration: BoxDecoration(
  454. color: const Color(0x33FF5252),
  455. borderRadius: BorderRadius.circular(8),
  456. border: Border.all(color: const Color(0x66FF5252)),
  457. ),
  458. child: Text(message,
  459. style: const TextStyle(color: Color(0xFFFF8A80), fontSize: 12)),
  460. );
  461. }
  462. }
  463. // ── 区块标题 ──────────────────────────────────────────────────
  464. class _SectionTitle extends StatelessWidget {
  465. const _SectionTitle({required this.title});
  466. final String title;
  467. @override
  468. Widget build(BuildContext context) {
  469. return Center(
  470. child: Row(
  471. mainAxisSize: MainAxisSize.min,
  472. crossAxisAlignment:CrossAxisAlignment.center,
  473. mainAxisAlignment: MainAxisAlignment.center,
  474. children: [
  475. // Container(
  476. // width: 14,
  477. // height: 3,
  478. // decoration: BoxDecoration(
  479. // color: AppColors.brand,
  480. // borderRadius: BorderRadius.circular(2))),
  481. Image.asset("assets/images/ico_airdrop_left.png",height:10,),
  482. const SizedBox(width: 5),
  483. Padding(padding:EdgeInsetsGeometry.only(bottom:3),child:Text(title,
  484. style: TextStyle(
  485. color: _airdropPrimaryText(context),
  486. fontSize: 14,
  487. fontWeight: FontWeight.w600)),),
  488. const SizedBox(width: 5),
  489. Image.asset("assets/images/ico_airdrop_right.png",height:10,),
  490. // Container(
  491. // width: 14,
  492. // height: 3,
  493. // decoration: BoxDecoration(
  494. // color: AppColors.brand,
  495. // borderRadius: BorderRadius.circular(2))),
  496. ],
  497. ),
  498. );
  499. }
  500. }
  501. // ── 任务卡片 ──────────────────────────────────────────────────
  502. class _TaskCard extends StatelessWidget {
  503. const _TaskCard({
  504. required this.title,
  505. required this.subtitle,
  506. required this.done,
  507. required this.btnText,
  508. this.onTap,
  509. });
  510. final String title;
  511. final String subtitle;
  512. final bool done;
  513. final String btnText;
  514. final VoidCallback? onTap;
  515. @override
  516. Widget build(BuildContext context) {
  517. return Container(
  518. padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
  519. decoration: BoxDecoration(
  520. color: _airdropCardBg(context),
  521. borderRadius: BorderRadius.circular(8)),
  522. child: Row(
  523. children: [
  524. Expanded(
  525. child: Column(
  526. crossAxisAlignment: CrossAxisAlignment.start,
  527. mainAxisSize: MainAxisSize.min,
  528. children: [
  529. Text(title,
  530. style: TextStyle(
  531. color: _airdropPrimaryText(context), fontSize: 13)),
  532. const SizedBox(height: 6),
  533. Text(
  534. done ? '$subtitle ✅' : subtitle,
  535. style:
  536. TextStyle(color: _airdropHintText(context), fontSize: 11),
  537. ),
  538. ],
  539. ),
  540. ),
  541. const SizedBox(width: 12),
  542. SizedBox(
  543. height: 30,
  544. child: Material(
  545. color: onTap == null
  546. ? AppColors.brand.withAlpha(128)
  547. : AppColors.brand,
  548. borderRadius: BorderRadius.circular(6),
  549. child: InkWell(
  550. onTap: onTap,
  551. borderRadius: BorderRadius.circular(6),
  552. child: Padding(
  553. padding: const EdgeInsets.symmetric(horizontal: 12),
  554. child: Center(
  555. child: Text(
  556. btnText,
  557. style: TextStyle(
  558. fontSize: 12,
  559. color: onTap == null
  560. ? Colors.black.withAlpha(128)
  561. : Colors.black,
  562. fontWeight: FontWeight.w500,
  563. ),
  564. ),
  565. ),
  566. ),
  567. ),
  568. ),
  569. ),
  570. ],
  571. ),
  572. );
  573. }
  574. }
  575. // ── 可领取金额面板 ────────────────────────────────────────────
  576. class _ClaimablePanel extends StatelessWidget {
  577. const _ClaimablePanel(
  578. {required this.label, required this.amount, required this.coinUnit});
  579. final String label;
  580. final String amount;
  581. final String coinUnit;
  582. @override
  583. Widget build(BuildContext context) {
  584. return Container(
  585. padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
  586. decoration: BoxDecoration(
  587. color: _airdropCardBg(context),
  588. borderRadius: BorderRadius.circular(8)),
  589. child: Row(
  590. children: [
  591. Expanded(
  592. child: Text(label,
  593. style:
  594. TextStyle(color: _airdropHintText(context), fontSize: 12)),
  595. ),
  596. Text('$amount $coinUnit',
  597. style: TextStyle(
  598. color: _airdropPrimaryText(context),
  599. fontSize: 18,
  600. fontWeight: FontWeight.w700)),
  601. ],
  602. ),
  603. );
  604. }
  605. }
  606. // ── 领取记录条目 ──────────────────────────────────────────────
  607. class _RecordCard extends StatelessWidget {
  608. const _RecordCard({required this.record});
  609. final AirdropRecordItem record;
  610. @override
  611. Widget build(BuildContext context) {
  612. final l10n = AppLocalizations.of(context);
  613. final statusText = switch (record.status) {
  614. 0 => l10n?.airdropStatusPending ?? 'Pending',
  615. 1 => l10n?.airdropStatusGranted ?? 'Granted',
  616. 2 => l10n?.airdropStatusReviewing ?? 'Reviewing',
  617. 3 => l10n?.airdropStatusRejected ?? 'Rejected',
  618. _ => '${record.status}',
  619. };
  620. return Container(
  621. margin: const EdgeInsets.only(bottom: 8),
  622. padding: const EdgeInsets.all(12),
  623. decoration: BoxDecoration(
  624. color: _airdropCardBg(context),
  625. borderRadius: BorderRadius.circular(8)),
  626. child: Row(
  627. children: [
  628. Expanded(
  629. child: Column(
  630. crossAxisAlignment: CrossAxisAlignment.start,
  631. mainAxisSize: MainAxisSize.min,
  632. children: [
  633. Text(
  634. '${record.amount} ${record.coinUnit ?? ''}',
  635. style: TextStyle(
  636. fontWeight: FontWeight.w600,
  637. color: _airdropPrimaryText(context)),
  638. ),
  639. const SizedBox(height: 4),
  640. Text(
  641. record.createTime ?? '--',
  642. style:
  643. TextStyle(color: _airdropHintText(context), fontSize: 12),
  644. ),
  645. ],
  646. ),
  647. ),
  648. Text(statusText, style: TextStyle(color: _airdropHintText(context))),
  649. ],
  650. ),
  651. );
  652. }
  653. }