Files
monisuo/flutter_monisuo/lib/ui/pages/asset/components/asset_dialogs.dart

665 lines
25 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:provider/provider.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../../../../core/theme/app_color_scheme.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/utils/toast_utils.dart';
import '../../../../providers/asset_provider.dart';
import '../../../components/glass_panel.dart';
import '../../../components/neon_glow.dart';
import '../../../shared/ui_constants.dart';
// ============================================
// Dialog helpers — shared sub-widgets
// ============================================
2026-04-07 01:05:05 +08:00
/// 信息行 — 用於對話框中顯示 label/value 鍵值對
class InfoRow extends StatelessWidget {
final String label;
final String value;
final bool isBold;
const InfoRow({
super.key,
required this.label,
required this.value,
this.isBold = false,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
Text(
value,
style: AppTextStyles.bodyMedium(context).copyWith(
fontWeight: isBold ? FontWeight.bold : FontWeight.w400,
),
),
],
);
}
}
2026-04-07 01:05:05 +08:00
/// 錢包地址卡片 — 用於充值結果對話框中展示錢包地址
class WalletAddressCard extends StatelessWidget {
final String address;
final String network;
const WalletAddressCard({
super.key,
required this.address,
required this.network,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(
color: colorScheme.outlineVariant.withValues(alpha: 0.3),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
address,
style: AppTextStyles.bodyMedium(context).copyWith(
fontFamily: 'monospace',
),
),
),
GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: address));
ToastUtils.showSuccess('地址已複製到剪貼板');
},
child: Container(
padding: const EdgeInsets.all(AppSpacing.xs),
decoration: BoxDecoration(
color: colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Icon(
LucideIcons.copy,
size: 16,
color: colorScheme.primary,
),
),
),
],
),
const SizedBox(height: AppSpacing.sm),
Text(
2026-04-07 01:05:05 +08:00
'網絡: $network',
style: AppTextStyles.bodySmall(context),
),
],
),
);
}
}
// ============================================
// Dialog functions — kept from original with style updates
// ============================================
2026-04-07 01:05:05 +08:00
/// 充值對話框
void showDepositDialog(BuildContext context) {
final amountController = TextEditingController();
final formKey = GlobalKey<ShadFormState>();
final colorScheme = Theme.of(context).colorScheme;
showShadDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
child: GlassPanel(
borderRadius: BorderRadius.circular(AppRadius.lg),
padding: const EdgeInsets.all(AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'充值',
style: AppTextStyles.headlineLarge(context).copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: AppSpacing.xs),
Text(
'Asset: USDT',
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
Container(
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Icon(
LucideIcons.wallet,
color: colorScheme.secondary,
),
),
],
),
const SizedBox(height: AppSpacing.lg),
ShadForm(
key: formKey,
child: ShadInputFormField(
id: 'amount',
controller: amountController,
2026-04-07 01:05:05 +08:00
label: const Text('充值金額'),
placeholder: const Text('最低 1000 USDT'),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: (v) {
2026-04-07 01:05:05 +08:00
if (v == null || v.isEmpty) return '請輸入金額';
final n = double.tryParse(v);
2026-04-07 01:05:05 +08:00
if (n == null || n <= 0) return '請輸入有效金額';
if (n < 1000) return '單筆最低充值1000 USDT';
return null;
},
),
),
const SizedBox(height: AppSpacing.lg),
Row(
children: [
Expanded(
child: NeonButton(
text: '取消',
type: NeonButtonType.outline,
onPressed: () => Navigator.of(ctx).pop(),
height: 48,
showGlow: false,
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: NeonButton(
text: '下一步',
type: NeonButtonType.primary,
onPressed: () async {
if (formKey.currentState!.saveAndValidate()) {
Navigator.of(ctx).pop();
final response = await context.read<AssetProvider>().deposit(
amount: amountController.text,
);
if (context.mounted) {
if (response.success && response.data != null) {
showDepositResultDialog(context, response.data!);
} else {
2026-04-07 01:05:05 +08:00
showResultDialog(context, '申請失敗', response.message);
}
}
}
},
height: 48,
showGlow: true,
),
),
],
),
],
),
),
),
);
}
2026-04-07 01:05:05 +08:00
/// 充值結果對話框 — 展示錢包地址和確認打款
void showDepositResultDialog(BuildContext context, Map<String, dynamic> data) {
final orderNo = data['orderNo'] as String? ?? '';
final amount = data['amount']?.toString() ?? '0.00';
final walletAddress = data['walletAddress'] as String? ?? '';
final walletNetwork = data['walletNetwork'] as String? ?? 'TRC20';
final colorScheme = Theme.of(context).colorScheme;
showShadDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
child: GlassPanel(
borderRadius: BorderRadius.circular(AppRadius.lg),
padding: const EdgeInsets.all(AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
NeonIcon(
icon: Icons.check_circle,
color: context.appColors.up,
size: 24,
),
const SizedBox(width: AppSpacing.sm),
Text(
2026-04-07 01:05:05 +08:00
'充值申請成功',
style: AppTextStyles.headlineLarge(context).copyWith(
fontWeight: FontWeight.w700,
),
),
],
),
const SizedBox(height: AppSpacing.lg),
2026-04-07 01:05:05 +08:00
InfoRow(label: '訂單號', value: orderNo),
const SizedBox(height: AppSpacing.sm),
2026-04-07 01:05:05 +08:00
InfoRow(label: '充值金額', value: '$amount USDT', isBold: true),
const SizedBox(height: AppSpacing.lg),
Text(
2026-04-07 01:05:05 +08:00
'請向以下地址轉賬:',
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: AppSpacing.sm),
WalletAddressCard(address: walletAddress, network: walletNetwork),
const SizedBox(height: AppSpacing.md),
Container(
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: AppColorScheme.warning.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(
color: AppColorScheme.warning.withValues(alpha: 0.2),
),
),
child: Row(
children: [
Icon(Icons.info_outline, size: 16, color: AppColorScheme.warning),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
2026-04-07 01:05:05 +08:00
'轉賬完成後請點擊"已打款"按鈕確認',
style: AppTextStyles.bodyMedium(context).copyWith(
color: AppColorScheme.warning,
),
),
),
],
),
),
const SizedBox(height: AppSpacing.lg),
Row(
children: [
Expanded(
child: NeonButton(
2026-04-07 01:05:05 +08:00
text: '稍後確認',
type: NeonButtonType.outline,
onPressed: () => Navigator.of(ctx).pop(),
height: 44,
showGlow: false,
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: NeonButton(
text: '已打款',
type: NeonButtonType.primary,
onPressed: () async {
Navigator.of(ctx).pop();
final response = await context.read<AssetProvider>().confirmPay(orderNo);
if (context.mounted) {
showResultDialog(
context,
2026-04-07 01:05:05 +08:00
response.success ? '確認成功' : '確認失敗',
response.success ? '請等待管理員審核' : response.message,
);
}
},
height: 44,
showGlow: true,
),
),
],
),
],
),
),
),
);
}
2026-04-07 01:05:05 +08:00
/// 提現對話框
void showWithdrawDialog(BuildContext context, String? balance) {
final amountController = TextEditingController();
final addressController = TextEditingController();
final contactController = TextEditingController();
final formKey = GlobalKey<ShadFormState>();
2026-04-07 01:05:05 +08:00
final feeNotifier = ValueNotifier<String>('提現將扣除10%手續費');
2026-04-06 09:31:14 +08:00
final networksNotifier = ValueNotifier<List<String>>([]);
final selectedNetworkNotifier = ValueNotifier<String?>(null);
final colorScheme = Theme.of(context).colorScheme;
2026-04-06 09:31:14 +08:00
amountController.addListener(() {
final amount = double.tryParse(amountController.text) ?? 0;
if (amount > 0) {
final fee = amount * 0.1;
final receivable = amount - fee;
2026-04-07 01:05:05 +08:00
feeNotifier.value = '手續費(10%): -${fee.toStringAsFixed(2)} USDT | 應收款: ${receivable.toStringAsFixed(2)} USDT';
2026-04-06 09:31:14 +08:00
} else {
2026-04-07 01:05:05 +08:00
feeNotifier.value = '提現將扣除10%手續費';
2026-04-06 09:31:14 +08:00
}
});
2026-04-07 01:05:05 +08:00
// 獲取網絡列表
2026-04-06 09:31:14 +08:00
context.read<AssetProvider>().getWalletNetworks().then((list) {
networksNotifier.value = list;
if (list.isNotEmpty) {
selectedNetworkNotifier.value = list.first;
}
});
showShadDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
child: GlassPanel(
borderRadius: BorderRadius.circular(AppRadius.lg),
padding: const EdgeInsets.all(AppSpacing.lg),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
2026-04-06 09:31:14 +08:00
Container(
padding: const EdgeInsets.all(AppSpacing.sm),
2026-04-06 09:31:14 +08:00
decoration: BoxDecoration(
color: colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
2026-04-06 09:31:14 +08:00
),
child: Icon(
LucideIcons.wallet,
color: colorScheme.primary,
),
),
const SizedBox(width: AppSpacing.sm),
Text(
2026-04-07 01:05:05 +08:00
'提現',
style: AppTextStyles.headlineLarge(context).copyWith(
fontWeight: FontWeight.w700,
),
),
2026-04-06 09:31:14 +08:00
],
),
const SizedBox(height: AppSpacing.xs),
Text(
2026-04-07 01:05:05 +08:00
'安全地將您的資產轉移到外部錢包地址',
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
if (balance != null) ...[
const SizedBox(height: AppSpacing.md),
Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: context.appColors.upBackground,
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(
color: context.appColors.up.withValues(alpha: 0.2),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
2026-04-07 01:05:05 +08:00
'可用餘額: ',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
Text(
'$balance USDT',
style: AppTextStyles.labelLarge(context).copyWith(
fontWeight: FontWeight.bold,
color: context.appColors.up,
),
2026-04-06 09:31:14 +08:00
),
],
),
),
],
const SizedBox(height: AppSpacing.lg),
ShadForm(
key: formKey,
child: Column(
children: [
ShadInputFormField(
id: 'amount',
controller: amountController,
2026-04-07 01:05:05 +08:00
label: const Text('提現金額'),
placeholder: const Text('請輸入提現金額(USDT)'),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: Validators.amount,
),
const SizedBox(height: AppSpacing.md),
2026-04-07 01:05:05 +08:00
// 手續費/應收款提示
2026-04-06 10:43:01 +08:00
ValueListenableBuilder<String>(
valueListenable: feeNotifier,
builder: (_, feeText, __) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHigh.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Row(
children: [
Icon(Icons.info_outline, size: 14, color: colorScheme.onSurfaceVariant),
const SizedBox(width: AppSpacing.xs),
Expanded(
child: Text(
feeText,
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
],
),
);
},
),
const SizedBox(height: AppSpacing.md),
2026-04-07 01:05:05 +08:00
// 提現網絡選擇
2026-04-06 10:43:01 +08:00
ValueListenableBuilder<List<String>>(
valueListenable: networksNotifier,
builder: (_, networks, __) {
if (networks.isEmpty) return const SizedBox.shrink();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
2026-04-07 01:05:05 +08:00
Text('提現網絡', style: AppTextStyles.bodyMedium(context).copyWith(
2026-04-06 10:43:01 +08:00
fontWeight: FontWeight.w500,
)),
const SizedBox(height: AppSpacing.xs),
ValueListenableBuilder<String?>(
valueListenable: selectedNetworkNotifier,
builder: (_, selected, __) {
2026-04-07 01:32:32 +08:00
return SizedBox(
width: double.infinity,
child: ShadSelect<String>(
placeholder: const Text('選擇提現網絡'),
initialValue: selected,
selectedOptionBuilder: (context, val) => Text(val),
onChanged: (value) {
if (value != null) selectedNetworkNotifier.value = value;
},
options: networks.map((n) => ShadOption(value: n, child: Text(n))).toList(),
),
2026-04-06 10:43:01 +08:00
);
},
),
],
);
},
),
const SizedBox(height: AppSpacing.md),
ShadInputFormField(
id: 'address',
controller: addressController,
2026-04-07 01:05:05 +08:00
label: const Text('目標地址'),
placeholder: const Text('請輸入提現地址'),
validator: (v) => Validators.required(v, '提現地址'),
),
const SizedBox(height: AppSpacing.md),
ShadInputFormField(
id: 'contact',
controller: contactController,
2026-04-07 01:05:05 +08:00
label: const Text('聯繫方式(可選)'),
placeholder: const Text('聯繫方式'),
),
2026-04-06 09:31:14 +08:00
],
),
),
const SizedBox(height: AppSpacing.lg),
Row(
children: [
Expanded(
child: NeonButton(
text: '取消',
type: NeonButtonType.outline,
onPressed: () => Navigator.of(ctx).pop(),
height: 44,
showGlow: false,
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: NeonButton(
text: '提交',
type: NeonButtonType.primary,
onPressed: () async {
if (formKey.currentState!.saveAndValidate()) {
Navigator.of(ctx).pop();
final response = await context.read<AssetProvider>().withdraw(
amount: amountController.text,
withdrawAddress: addressController.text,
withdrawContact: contactController.text.isNotEmpty
? contactController.text
: null,
2026-04-06 10:43:01 +08:00
network: selectedNetworkNotifier.value,
);
if (context.mounted) {
showResultDialog(
context,
2026-04-07 01:05:05 +08:00
response.success ? '申請成功' : '申請失敗',
response.success ? '請等待管理員審批' : response.message,
);
}
}
},
height: 44,
showGlow: true,
2026-04-06 09:31:14 +08:00
),
),
],
),
const SizedBox(height: AppSpacing.md),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.verified_user,
size: 12,
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
),
const SizedBox(width: AppSpacing.xs),
],
),
],
),
),
),
),
);
}
2026-04-07 01:05:05 +08:00
/// 通用結果對話框 — 展示操作成功/失敗信息
void showResultDialog(BuildContext context, String title, String? message) {
final colorScheme = Theme.of(context).colorScheme;
showShadDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
child: GlassPanel(
borderRadius: BorderRadius.circular(AppRadius.lg),
padding: const EdgeInsets.all(AppSpacing.lg),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: AppTextStyles.headlineLarge(context).copyWith(
fontWeight: FontWeight.w700,
),
),
if (message != null) ...[
const SizedBox(height: AppSpacing.sm),
Text(
message,
style: AppTextStyles.bodyLarge(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
const SizedBox(height: AppSpacing.lg),
SizedBox(
width: double.infinity,
child: NeonButton(
2026-04-07 01:05:05 +08:00
text: '確定',
type: NeonButtonType.primary,
onPressed: () => Navigator.of(ctx).pop(),
height: 44,
showGlow: false,
),
),
],
),
),
),
);
}