deposit_history_screen.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.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 '../../../data/models/asset/recharge_record.dart';
  8. import '../../../providers/deposit_provider.dart';
  9. import '../../widgets/common/app_refresh_indicator.dart';
  10. class DepositHistoryScreen extends ConsumerStatefulWidget {
  11. const DepositHistoryScreen({super.key});
  12. @override
  13. ConsumerState<DepositHistoryScreen> createState() =>
  14. _DepositHistoryScreenState();
  15. }
  16. class _DepositHistoryScreenState extends ConsumerState<DepositHistoryScreen> {
  17. @override
  18. void initState() {
  19. super.initState();
  20. // 进入页面时自动刷新一次
  21. Future.microtask(() {
  22. ref.read(rechargeHistoryProvider.notifier).refresh();
  23. });
  24. }
  25. @override
  26. Widget build(BuildContext context) {
  27. final state = ref.watch(rechargeHistoryProvider);
  28. final notifier = ref.read(rechargeHistoryProvider.notifier);
  29. return Scaffold(
  30. appBar: AppBar(
  31. leading: IconButton(
  32. icon: const Icon(Icons.chevron_left, size: 28),
  33. onPressed: () => context.pop(),
  34. ),
  35. title: Text(AppLocalizations.of(context)!.depositRecord, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
  36. centerTitle: true,
  37. ),
  38. body: _buildBody(context, state, notifier),
  39. );
  40. }
  41. Widget _buildBody(BuildContext context, RechargeHistoryState state, RechargeHistoryNotifier notifier) {
  42. final cs = Theme.of(context).colorScheme;
  43. // Loading(首次)
  44. if (state.isLoading && state.records.isEmpty) {
  45. return const Center(child: CircularProgressIndicator());
  46. }
  47. // Error(首次)
  48. if (state.errorMessage != null && state.records.isEmpty) {
  49. return AppRefreshIndicator(
  50. onRefresh: notifier.refresh,
  51. child: SingleChildScrollView(
  52. physics: const AlwaysScrollableScrollPhysics(),
  53. child: Center(
  54. child: Column(
  55. mainAxisSize: MainAxisSize.min,
  56. mainAxisAlignment: MainAxisAlignment.center,
  57. children: [
  58. Text(state.errorMessage!, style: TextStyle(color: cs.onSurface.withAlpha(153))),
  59. const SizedBox(height: 16),
  60. ElevatedButton(onPressed: notifier.refresh, child: Text(AppLocalizations.of(context)!.retry)),
  61. ],
  62. ),
  63. ),
  64. ),
  65. );
  66. }
  67. // Empty - with RefreshIndicator
  68. if (state.records.isEmpty) {
  69. return AppRefreshIndicator(
  70. onRefresh: notifier.refresh,
  71. child: SingleChildScrollView(
  72. physics: const AlwaysScrollableScrollPhysics(),
  73. child: Center(
  74. child: Padding(
  75. padding: const EdgeInsets.symmetric(vertical: 100),
  76. child: Text(AppLocalizations.of(context)!.noRecord, style: TextStyle(color: cs.onSurface.withAlpha(120), fontSize: 15)),
  77. ),
  78. ),
  79. ),
  80. );
  81. }
  82. return AppRefreshIndicator(
  83. onRefresh: notifier.refresh,
  84. child: NotificationListener<ScrollNotification>(
  85. onNotification: (notification) {
  86. if (notification is ScrollEndNotification &&
  87. notification.metrics.pixels >= notification.metrics.maxScrollExtent - 100) {
  88. notifier.loadMore();
  89. }
  90. return false;
  91. },
  92. child: ListView.separated(
  93. padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
  94. itemCount: state.records.length + (state.hasMore ? 1 : 0),
  95. separatorBuilder: (_, __) => Divider(height: 1, color: cs.outline.withAlpha(30)),
  96. itemBuilder: (_, i) {
  97. if (i >= state.records.length) {
  98. return const Padding(
  99. padding: EdgeInsets.symmetric(vertical: 16),
  100. child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
  101. );
  102. }
  103. return _RecordTile(record: state.records[i]);
  104. },
  105. ),
  106. ),
  107. );
  108. }
  109. }
  110. // ── 充值记录行 ──────────────────────────────────────────────────
  111. class _RecordTile extends StatelessWidget {
  112. const _RecordTile({required this.record});
  113. final RechargeRecord record;
  114. @override
  115. Widget build(BuildContext context) {
  116. final cs = Theme.of(context).colorScheme;
  117. return GestureDetector(
  118. behavior: HitTestBehavior.opaque,
  119. onTap: () {
  120. context.push('/asset/deposit/detail', extra: record);
  121. },
  122. child: Padding(
  123. padding: const EdgeInsets.symmetric(vertical: 14),
  124. child: Row(
  125. crossAxisAlignment: CrossAxisAlignment.start,
  126. children: [
  127. // 左侧:标题 + 时间
  128. Expanded(
  129. child: Column(
  130. crossAxisAlignment: CrossAxisAlignment.start,
  131. children: [
  132. Text(
  133. 'USDT ${AppLocalizations.of(context)!.recharge}',
  134. style: TextStyle(color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w600),
  135. ),
  136. const SizedBox(height: 4),
  137. Text(
  138. record.createTime,
  139. style: TextStyle(color: cs.onSurface.withAlpha(120), fontSize: 12),
  140. ),
  141. if (record.txId.isNotEmpty) ...[
  142. const SizedBox(height: 4),
  143. GestureDetector(
  144. onTap: () {
  145. Clipboard.setData(ClipboardData(text: record.txId));
  146. ScaffoldMessenger.of(context).showSnackBar(
  147. SnackBar(content: Text(AppLocalizations.of(context)!.hashCopied), duration: const Duration(seconds: 1)),
  148. );
  149. },
  150. child: Text(
  151. 'TX: ${_truncate(record.txId)}',
  152. style: TextStyle(color: cs.onSurface.withAlpha(100), fontSize: 11),
  153. ),
  154. ),
  155. ],
  156. ],
  157. ),
  158. ),
  159. // 右侧:金额
  160. Text(
  161. '+${record.amount}',
  162. style: const TextStyle(color: AppColors.rise, fontSize: 15, fontWeight: FontWeight.w600),
  163. ),
  164. ],
  165. ),
  166. ),
  167. );
  168. }
  169. String _truncate(String s) {
  170. if (s.length <= 16) return s;
  171. return '${s.substring(0, 8)}...${s.substring(s.length - 8)}';
  172. }
  173. }