rwadurian/frontend/mining-app/lib/presentation/pages/c2c/c2c_publish_page.dart

819 lines
27 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/utils/format_utils.dart';
import '../../providers/c2c_providers.dart';
import '../../providers/user_providers.dart';
import '../../providers/asset_providers.dart';
import '../../providers/trading_providers.dart';
class C2cPublishPage extends ConsumerStatefulWidget {
const C2cPublishPage({super.key});
@override
ConsumerState<C2cPublishPage> createState() => _C2cPublishPageState();
}
class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
static const Color _orange = Color(0xFFFF6B00);
static const Color _green = Color(0xFF10B981);
static const Color _red = Color(0xFFEF4444);
static const Color _darkText = Color(0xFF1F2937);
static const Color _grayText = Color(0xFF6B7280);
static const Color _bgGray = Color(0xFFF3F4F6);
int _selectedType = 1; // 0: 买入, 1: 卖出
final _priceController = TextEditingController();
final _quantityController = TextEditingController();
final _remarkController = TextEditingController();
// 收款信息(卖单必填)
String _paymentMethod = 'ALIPAY'; // ALIPAY, WECHAT, BANK
final _paymentAccountController = TextEditingController();
final _paymentRealNameController = TextEditingController();
@override
void dispose() {
_priceController.dispose();
_quantityController.dispose();
_remarkController.dispose();
_paymentAccountController.dispose();
_paymentRealNameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final user = ref.watch(userNotifierProvider);
final accountSequence = user.accountSequence ?? '';
final assetAsync = ref.watch(accountAssetProvider(accountSequence));
final priceAsync = ref.watch(currentPriceProvider);
final c2cState = ref.watch(c2cTradingNotifierProvider);
final asset = assetAsync.valueOrNull;
final currentPrice = priceAsync.valueOrNull?.price ?? '0';
final availableShares = asset?.availableShares ?? '0';
final availableCash = asset?.availableCash ?? '0';
// 设置默认价格
if (_priceController.text.isEmpty && currentPrice != '0') {
_priceController.text = currentPrice;
}
return Scaffold(
backgroundColor: _bgGray,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: _darkText),
onPressed: () => context.pop(),
),
title: const Text(
'发布广告',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 16),
// 交易类型选择
_buildTypeSelector(),
const SizedBox(height: 16),
// 可用余额
_buildBalanceCard(availableShares, availableCash),
const SizedBox(height: 16),
// 价格输入
_buildPriceInput(currentPrice),
const SizedBox(height: 16),
// 数量输入
_buildQuantityInput(availableShares, availableCash, currentPrice),
const SizedBox(height: 16),
// 收款信息(卖单必填)
if (_selectedType == 1) ...[
_buildPaymentInfoInput(),
const SizedBox(height: 16),
],
// 备注
_buildRemarkInput(),
const SizedBox(height: 16),
// 预估信息
_buildEstimateCard(),
const SizedBox(height: 24),
// 发布按钮
_buildPublishButton(c2cState),
const SizedBox(height: 16),
// 提示信息
_buildTips(),
const SizedBox(height: 24),
],
),
),
);
}
Widget _buildTypeSelector() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => setState(() => _selectedType = 0),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: _selectedType == 0 ? _green : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'我要买入',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _selectedType == 0 ? Colors.white : _grayText,
),
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () => setState(() => _selectedType = 1),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: _selectedType == 1 ? _red : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'我要卖出',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _selectedType == 1 ? Colors.white : _grayText,
),
),
),
),
),
],
),
);
}
Widget _buildBalanceCard(String availableShares, String availableCash) {
final isBuy = _selectedType == 0;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
isBuy ? '可用积分值' : '可用积分股',
style: const TextStyle(fontSize: 14, color: _grayText),
),
Text(
isBuy ? formatAmount(availableCash) : formatAmount(availableShares),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: isBuy ? _green : _orange,
),
),
],
),
);
}
Widget _buildPriceInput(String currentPrice) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'单价',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
Text(
'当前价: ${formatPrice(currentPrice)}',
style: const TextStyle(fontSize: 12, color: _grayText),
),
],
),
const SizedBox(height: 12),
TextField(
controller: _priceController,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,8}')),
],
decoration: InputDecoration(
hintText: '请输入单价',
hintStyle: const TextStyle(color: _grayText),
filled: true,
fillColor: _bgGray,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
suffixText: '积分值/股',
suffixStyle: const TextStyle(color: _grayText, fontSize: 14),
),
onChanged: (_) => setState(() {}),
),
],
),
);
}
Widget _buildQuantityInput(
String availableShares,
String availableCash,
String currentPrice,
) {
final isBuy = _selectedType == 0;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'数量',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
const SizedBox(height: 12),
TextField(
controller: _quantityController,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,4}')),
],
decoration: InputDecoration(
hintText: '请输入数量',
hintStyle: const TextStyle(color: _grayText),
filled: true,
fillColor: _bgGray,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
suffixIcon: TextButton(
onPressed: () {
if (isBuy) {
// 买入:根据可用积分值计算可买数量
final price = double.tryParse(_priceController.text) ?? 0;
final cash = double.tryParse(availableCash) ?? 0;
if (price > 0) {
_quantityController.text = (cash / price).toStringAsFixed(4);
}
} else {
// 卖出:填入全部可用积分股
_quantityController.text = availableShares;
}
setState(() {});
},
child: const Text(
'全部',
style: TextStyle(
color: _orange,
fontWeight: FontWeight.w500,
),
),
),
suffixText: '积分股',
suffixStyle: const TextStyle(color: _grayText, fontSize: 14),
),
onChanged: (_) => setState(() {}),
),
],
),
);
}
Widget _buildPaymentInfoInput() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: const [
Text(
'收款信息',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
SizedBox(width: 8),
Text(
'(必填)',
style: TextStyle(fontSize: 12, color: _red),
),
],
),
const SizedBox(height: 16),
// 收款方式选择
const Text(
'收款方式',
style: TextStyle(fontSize: 14, color: _grayText),
),
const SizedBox(height: 8),
Row(
children: [
_buildPaymentMethodChip('ALIPAY', '支付宝', Icons.account_balance_wallet),
const SizedBox(width: 12),
_buildPaymentMethodChip('WECHAT', '微信', Icons.chat_bubble),
const SizedBox(width: 12),
_buildPaymentMethodChip('BANK', '银行卡', Icons.credit_card),
],
),
const SizedBox(height: 16),
// 收款账号
const Text(
'收款账号',
style: TextStyle(fontSize: 14, color: _grayText),
),
const SizedBox(height: 8),
TextField(
controller: _paymentAccountController,
decoration: InputDecoration(
hintText: _paymentMethod == 'BANK' ? '请输入银行卡号' : '请输入收款账号',
hintStyle: const TextStyle(color: _grayText),
filled: true,
fillColor: _bgGray,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
),
),
const SizedBox(height: 16),
// 收款人实名
const Text(
'收款人姓名',
style: TextStyle(fontSize: 14, color: _grayText),
),
const SizedBox(height: 8),
TextField(
controller: _paymentRealNameController,
decoration: InputDecoration(
hintText: '请输入收款人真实姓名',
hintStyle: const TextStyle(color: _grayText),
filled: true,
fillColor: _bgGray,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: const [
Icon(Icons.info_outline, size: 16, color: _orange),
SizedBox(width: 8),
Expanded(
child: Text(
'买家会根据您填写的收款信息进行付款,请确保信息准确',
style: TextStyle(fontSize: 12, color: _orange),
),
),
],
),
),
],
),
);
}
Widget _buildPaymentMethodChip(String value, String label, IconData icon) {
final isSelected = _paymentMethod == value;
return GestureDetector(
onTap: () => setState(() => _paymentMethod = value),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: isSelected ? _orange : _bgGray,
borderRadius: BorderRadius.circular(8),
border: isSelected ? null : Border.all(color: Colors.grey.shade300),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 18,
color: isSelected ? Colors.white : _grayText,
),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
fontSize: 13,
color: isSelected ? Colors.white : _darkText,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
],
),
),
);
}
Widget _buildRemarkInput() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'备注 (可选)',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
const SizedBox(height: 12),
TextField(
controller: _remarkController,
maxLength: 100,
maxLines: 2,
decoration: InputDecoration(
hintText: '添加交易说明,如联系方式等',
hintStyle: const TextStyle(color: _grayText),
filled: true,
fillColor: _bgGray,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
counterStyle: const TextStyle(color: _grayText),
),
),
],
),
);
}
Widget _buildEstimateCard() {
final price = double.tryParse(_priceController.text) ?? 0;
final quantity = double.tryParse(_quantityController.text) ?? 0;
final total = price * quantity;
final isBuy = _selectedType == 0;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _orange.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: _orange.withOpacity(0.2)),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('交易总额', style: TextStyle(fontSize: 14, color: _grayText)),
Text(
'${formatAmount(total.toString())} 积分值',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: _orange,
),
),
],
),
const SizedBox(height: 8),
Divider(color: _orange.withOpacity(0.2)),
const SizedBox(height: 8),
Text(
isBuy
? '发布后,其他用户可以接单卖出积分股给您'
: '发布后,其他用户可以接单用积分值购买您的积分股',
style: const TextStyle(fontSize: 12, color: _grayText),
),
],
),
);
}
Widget _buildPublishButton(C2cTradingState c2cState) {
final price = double.tryParse(_priceController.text) ?? 0;
final quantity = double.tryParse(_quantityController.text) ?? 0;
final isSell = _selectedType == 1;
// 卖单需要验证收款信息
bool isValid = price > 0 && quantity > 0;
if (isSell) {
isValid = isValid &&
_paymentAccountController.text.trim().isNotEmpty &&
_paymentRealNameController.text.trim().isNotEmpty;
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: isValid && !c2cState.isLoading ? _handlePublish : null,
style: ElevatedButton.styleFrom(
backgroundColor: _selectedType == 0 ? _green : _red,
foregroundColor: Colors.white,
disabledBackgroundColor: (_selectedType == 0 ? _green : _red).withOpacity(0.4),
disabledForegroundColor: Colors.white70,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: c2cState.isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: Text(
_selectedType == 0 ? '发布买入广告' : '发布卖出广告',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
Widget _buildTips() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: const [
Icon(Icons.info_outline, size: 16, color: _orange),
SizedBox(width: 8),
Text(
'交易说明',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _orange,
),
),
],
),
const SizedBox(height: 8),
const Text(
'1. 发布广告后,您的资产将被冻结直到交易完成或取消\n'
'2. 其他用户接单后,需在规定时间内完成交易\n'
'3. 买方需先转账积分值,卖方确认收款后积分股自动划转\n'
'4. 如遇问题,请联系客服处理',
style: TextStyle(
fontSize: 12,
color: _grayText,
height: 1.5,
),
),
],
),
);
}
Future<void> _handlePublish() async {
final price = _priceController.text.trim();
final quantity = _quantityController.text.trim();
final remark = _remarkController.text.trim();
final type = _selectedType == 0 ? 'BUY' : 'SELL';
final isSell = _selectedType == 1;
// 卖单验证收款信息
if (isSell) {
if (_paymentAccountController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请填写收款账号'), backgroundColor: _red),
);
return;
}
if (_paymentRealNameController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请填写收款人姓名'), backgroundColor: _red),
);
return;
}
}
final paymentMethodText = _paymentMethod == 'ALIPAY'
? '支付宝'
: _paymentMethod == 'WECHAT'
? '微信'
: '银行卡';
// 确认对话框
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(_selectedType == 0 ? '确认发布买入广告' : '确认发布卖出广告'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('单价: $price 积分值/股'),
const SizedBox(height: 8),
Text('数量: $quantity 积分股'),
const SizedBox(height: 8),
Text(
'总额: ${formatAmount((double.parse(price) * double.parse(quantity)).toString())} 积分值',
style: const TextStyle(fontWeight: FontWeight.bold),
),
if (isSell) ...[
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 8),
Text('收款方式: $paymentMethodText'),
const SizedBox(height: 4),
Text('收款账号: ${_paymentAccountController.text.trim()}'),
const SizedBox(height: 4),
Text('收款人: ${_paymentRealNameController.text.trim()}'),
],
const SizedBox(height: 16),
Text(
_selectedType == 0
? '发布后,您的积分值将被冻结'
: '发布后,您的积分股将被冻结',
style: TextStyle(fontSize: 12, color: _grayText),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text(
'确认发布',
style: TextStyle(color: _selectedType == 0 ? _green : _red),
),
),
],
),
);
if (confirmed != true) return;
final notifier = ref.read(c2cTradingNotifierProvider.notifier);
final success = await notifier.createOrder(
type: type,
price: price,
quantity: quantity,
// 收款信息(卖单时传递)
paymentMethod: isSell ? _paymentMethod : null,
paymentAccount: isSell ? _paymentAccountController.text.trim() : null,
paymentRealName: isSell ? _paymentRealNameController.text.trim() : null,
remark: remark.isEmpty ? null : remark,
);
if (success && mounted) {
// 刷新列表
ref.invalidate(c2cOrdersProvider(type));
ref.invalidate(myC2cOrdersProvider);
// 刷新资产
final user = ref.read(userNotifierProvider);
ref.invalidate(accountAssetProvider(user.accountSequence ?? ''));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('广告发布成功'),
backgroundColor: _green,
),
);
context.pop();
} else if (mounted) {
final error = ref.read(c2cTradingNotifierProvider).error;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('发布失败: ${error ?? '未知错误'}'),
backgroundColor: _red,
),
);
}
}
}