diff --git a/lib/common/transaction.dart b/lib/common/transaction.dart index 2ccf6f3..ee0bd3a 100644 --- a/lib/common/transaction.dart +++ b/lib/common/transaction.dart @@ -41,7 +41,7 @@ class TransactionHelper { //无 nonce、有 remark(成块补零) static const int padZerosNoNonceHasRemark = 18; - static String getTransaction(String fromAddress, String toAddress, String remark, double value, bip32.BIP32 wallet, String nonce) { + static String getTransaction(String fromAddress, String toAddress, String remark, double value, bip32.BIP32 wallet, String nonce, double fee) { // print('getTransaction: $fromAddress, $toAddress, $remark, $value, $nonce'); bool isPubKeyEven = wallet.publicKey[0] % 2 == 0; String from = checkBase58Address(fromAddress); @@ -71,7 +71,8 @@ class TransactionHelper { } sb += HEX.encode(timeBytes.buffer.asUint8List()); - sb += "0000000000000000"; + // 添加可变手续费 + sb += fee2Bytes(fee); // print('header: $sb'); // nonce:前面补 48 个 0 // 由于rpc查询出来的nonce(rpc查出来的到的结果是String类型),会放在该32字节的后八个字段,然后前面24个字节的零,这后八个字节存放nonce的方式是小端序存放。 @@ -146,6 +147,19 @@ class TransactionHelper { return res + amount; } + static String fee2Bytes(double fee) { + final amountValue = (fee * 1000000000).round(); + + final valBytes = Uint8List(8); + ByteData.view(valBytes.buffer).setUint64(0, amountValue, Endian.little); + + final hexString = valBytes.map((byte) { + return byte.toRadixString(16).padLeft(2, '0'); + }).join(); + + return hexString; + } + static Int64 getCurrentTimestamp() { var t0 = DateTime.now().toUtc().millisecondsSinceEpoch * 1000000; Int64 t = Int64(t0); diff --git a/lib/desktop/desktop_transaction_detail_page.dart b/lib/desktop/desktop_transaction_detail_page.dart index da75602..f509e70 100644 --- a/lib/desktop/desktop_transaction_detail_page.dart +++ b/lib/desktop/desktop_transaction_detail_page.dart @@ -162,7 +162,7 @@ class DesktopTransactionDetailPageWidgetState extends State { final TextEditingController controller0 = TextEditingController(); final TextEditingController controller1 = TextEditingController(); final TextEditingController controller2 = TextEditingController(); + final TextEditingController controller3 = TextEditingController(); + String address = ""; String amount = ""; String remark = ""; + String fee = ""; + String averageFee = "0.1 XDAG"; // 平均手续费 + bool load = false; + bool showFeeBox = false; // 控制是否显示自定义手续费输入框 + bool isLoadingAverageFee = false; // 控制平均手续费加载状态 Isolate? isolate; final dio = Dio(); @@ -522,6 +529,7 @@ class _SendCardState extends State { @override void initState() { super.initState(); + fetchAverageFee(); // 调用 RPC 方法(页面初始化时触发) } @override @@ -532,6 +540,45 @@ class _SendCardState extends State { super.dispose(); } + Future fetchAverageFee() async { + setState(() { + isLoadingAverageFee = true; + }); + try { + ConfigModal config = Provider.of(context, listen: false); + String rpcURL = config.getCurrentRpc(); + Response response = await dio.post( + rpcURL, + cancelToken: cancelToken, + data: { + "jsonrpc": "2.0", + "method": "xdag_getAverageFee", + "params": [], + "id": 1 + }, + ); + if (response.data != null && response.data['result'] != null) { + double feeValue = double.tryParse(response.data['result'].toString()) ?? 0.0; + setState(() { + averageFee = "${feeValue.toStringAsFixed(2)} XDAG"; + }); + } else { + setState(() { + averageFee = "0.10 XDAG"; + }); + } + } catch (e) { + debugPrint("获取平均手续费失败:$e"); + setState(() { + averageFee = "0.10 XDAG"; + }); + } finally { + setState(() { + isLoadingAverageFee = false; + }); + } + } + static void isolateFunction(SendPort sendPort) async { final receivePort = ReceivePort(); sendPort.send(receivePort.sendPort); @@ -543,9 +590,11 @@ class _SendCardState extends State { String fromAddress = data[3] as String; String remark = data[4] as String; String nonce = data[5] as String; + String fee = data[6] as String; + bool isPrivateKey = res.trim().split(' ').length == 1; bip32.BIP32 wallet = Helper.createWallet(isPrivate: isPrivateKey, content: res); - String result = TransactionHelper.getTransaction(fromAddress, toAddress, remark, double.parse(amount), wallet, nonce); + String result = TransactionHelper.getTransaction(fromAddress, toAddress, remark, double.parse(amount), wallet, nonce, double.parse(fee)); sendPort.send(['success', result]); }); } @@ -569,9 +618,11 @@ class _SendCardState extends State { receivePort.listen((data) async { var sendAmount = amount; var sendRemark = remark; + var sendFee = fee; + if (data is SendPort) { var subSendPort = data; - subSendPort.send([res, toAddress, amount, fromAddress, remark, nonce]); + subSendPort.send([res, toAddress, amount, fromAddress, remark, nonce, fee]); } else if (data is List) { String result = data[1]; try { @@ -587,15 +638,18 @@ class _SendCardState extends State { var res = response.data['result'] as String; // print(res); if (res.length == 32 && res.trim().split(' ').length == 1) { - var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(sendAmount.toString()), address: fromAddress, status: 'pending', from: fromAddress, to: toAddress, type: 0, hash: '', fee: 0, blockAddress: res, remark: sendRemark); + var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(sendAmount.toString()), address: fromAddress, status: 'pending', from: fromAddress, to: toAddress, type: 0, hash: '', fee: double.parse(sendFee), blockAddress: res, remark: sendRemark); + controller0.clear(); controller1.clear(); controller2.clear(); + controller3.clear(); setState(() { load = false; amount = ''; remark = ''; address = ''; + fee = ''; }); showDialog(context: context, builder: (BuildContext context) => DesktopTransactionDetailPageWidget(transaction: transactionItem, address: fromAddress)); @@ -610,11 +664,13 @@ class _SendCardState extends State { controller0.clear(); controller1.clear(); controller2.clear(); + controller3.clear(); setState(() { load = false; amount = ''; remark = ''; address = ''; + fee = ''; }); } } @@ -656,10 +712,12 @@ class _SendCardState extends State { ), Expanded( flex: 1, - child: Padding( + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 10), Text(AppLocalizations.of(context)!.to, style: Helper.fitChineseFont(context, const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w500))), @@ -723,7 +781,7 @@ class _SendCardState extends State { ], decoration: InputDecoration( filled: true, - contentPadding: const EdgeInsets.fromLTRB(15, 40, 15, 40), + contentPadding: const EdgeInsets.fromLTRB(15, 30, 15, 30), fillColor: DarkColors.bgColor, hintText: 'XDAG', hintStyle: Helper.fitChineseFont(context, const TextStyle(decoration: TextDecoration.none, fontSize: 32, fontWeight: FontWeight.w500, color: Colors.white54)), @@ -731,6 +789,105 @@ class _SendCardState extends State { focusedBorder: const OutlineInputBorder(borderSide: BorderSide(color: DarkColors.mainColor, width: 1), borderRadius: BorderRadius.all(Radius.circular(10))), ), ), + // 控制手续费框显示 + const SizedBox(height: 15), + Row( + children: [ + Checkbox( + value: showFeeBox, + onChanged: (value) { + setState(() { + showFeeBox = value ?? false; + if (!showFeeBox) { + fee = ""; + controller3.clear(); + } + }); + }, + activeColor: DarkColors.mainColor, + checkColor: Colors.white, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.express_fee, + style: Helper.fitChineseFont(context, const TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500, + )), + ), + const SizedBox(height: 4), + Text( + "${AppLocalizations.of(context)!.average_fee}: $averageFee", + style: Helper.fitChineseFont(context, const TextStyle( + fontSize: 14, + color: Colors.white54, + )), + ), + ], + ), + ], + ), + // 手续费框 + if (showFeeBox) ...[ + const SizedBox(height: 10), + Container( + constraints: const BoxConstraints(maxHeight: 150), + decoration: BoxDecoration( + color: DarkColors.bgColor, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: DarkColors.mainColor, width: 1), + ), + child: SingleChildScrollView( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AutoSizeTextField( + controller: controller3, + onChanged: (value) { + setState(() => fee = value); + }, + minFontSize: 16, + maxLines: null, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + keyboardAppearance: Brightness.dark, + style: Helper.fitChineseFont(context, const TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500, + )), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}')), + ], + decoration: InputDecoration( + filled: true, + fillColor: DarkColors.blockColor, + hintText: AppLocalizations.of(context)!.fee, + hintStyle: const TextStyle(color: Colors.white54), + border: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + ), + ), + const SizedBox(height: 10), + Text( + "${AppLocalizations.of(context)!.fee_explanation}", + style: Helper.fitChineseFont(context, const TextStyle( + fontSize: 14, + color: Colors.white70, + height: 1.5, + )), + ), + ], + ), + ), + ), + ], const SizedBox(height: 15), Text(AppLocalizations.of(context)!.remark, style: Helper.fitChineseFont(context, const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w500))), const SizedBox(height: 10), @@ -767,7 +924,7 @@ class _SendCardState extends State { focusedBorder: const OutlineInputBorder(borderSide: BorderSide(color: DarkColors.mainColor, width: 1), borderRadius: BorderRadius.all(Radius.circular(10))), ), ), - const Spacer(), + const SizedBox(height: 30), Row( children: [ const Spacer(), @@ -783,9 +940,26 @@ class _SendCardState extends State { showDialog(context: context, builder: (context) => DesktopAlertModal(title: AppLocalizations.of(context)!.error, content: AppLocalizations.of(context)!.walletAddressError)); return; } + if (showFeeBox) { + // 必须输入有效数字 + if (fee == '' || fee.isEmpty || double.tryParse(fee) == null) { + if (context.mounted) { + showDialog( + context: context, + builder: (context) => DesktopAlertModal( + title: AppLocalizations.of(context)!.error, + content: AppLocalizations.of(context)!.invalid_fee, + ), + ); + } + return; + } + } else { + fee = '0'; + } WalletModal walletModal = Provider.of(context, listen: false); Wallet wallet = walletModal.getWallet(); - var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(amount.toString()), address: wallet.address, status: 'pending', from: wallet.address, to: address, type: 0, hash: '', fee: 0, blockAddress: "", remark: remark); + var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(amount.toString()), address: wallet.address, status: 'pending', from: wallet.address, to: address, type: 0, hash: '', fee: double.parse(fee), blockAddress: "", remark: remark); var f = await showDialog( context: context, builder: (context) => DesktopTransactionDetail(transaction: transactionItem), @@ -829,4 +1003,4 @@ class _SendCardState extends State { ], )); } -} +} \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9e83daa..0296a21 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -150,5 +150,9 @@ "transactions": "Transaktionen", "forget_the_password": "Passwort vergessen", "time_zone": "Zeitzone", - "transactions_in_progress": "Transaktionen in Arbeit" + "transactions_in_progress": "Transaktionen in Arbeit", + "express_fee": "Variable Gebühr", + "invalid_fee": "Bitte geben Sie eine gültige Gebühr ein", + "average_fee": "Durchschnittliche Gebühr", + "fee_explanation": "Gebührenerklärung:\n1. Zur Grundgebühr von 0,1 XDAG müssen Eingabegebühren hinzugerechnet werden\n2. Je höher die Gebühr, desto schneller die Transaktionsbestätigung" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c6af5d4..d8121f0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -150,5 +150,9 @@ "transactions": "Transactions", "forget_the_password": "forget_the_password", "time_zone": "Time Zone", - "transactions_in_progress": "Transactions in progress" + "transactions_in_progress": "Transactions in progress", + "express_fee": "Expression Fee", + "invalid_fee": "Please enter a invalid fee", + "average_fee": "Average Fee", + "fee_explanation": "Fee Explanation:\n1. Input fees must be added to the base fee of 0.1 XDAG\n2. The higher the fee, the faster the transaction confirmation speed" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 23d7812..eadf1da 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -150,5 +150,9 @@ "transactions": "Transacciones", "forget_the_password": "Olvidé la contraseña", "time_zone": "Zona horaria", - "transactions_in_progress": "Transacciones en progreso" + "transactions_in_progress": "Transacciones en progreso", + "express_fee": "Tarifa exprés", + "invalid_fee": "Por favor, introduzca una tarifa válida", + "average_fee": "Tarifa promedio", + "fee_explanation": "Explicación de la tarifa:\n1. Las tarifas de entrada deben añadirse a la tarifa base de 0,1 XDAG.\n2. Cuanto mayor sea la tarifa, más rápida será la confirmación de la transacción." } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 2701fb5..22fcc3d 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -150,5 +150,9 @@ "transactions": "Transactions", "forget_the_password": "Mot de passe oublié", "time_zone": "Fuseau horaire", - "transactions_in_progress": "Transactions en cours" + "transactions_in_progress": "Transactions en cours", + "express_fee": "Frais d'expression", + "invalid_fee": "Veuillez saisir des frais valides", + "average_fee": "Frais moyens", + "fee_explanation": "Explication des frais :\n1. Les frais de saisie doivent être ajoutés aux frais de base de 0,1 XDAG.\n2. Plus les frais sont élevés, plus la confirmation de la transaction est rapide." } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index d25d861..d46ddfc 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -150,5 +150,9 @@ "transactions": "Transazioni", "forget_the_password": "Dimentica la password", "time_zone": "Fuso orario", - "transactions_in_progress": "Transazioni in corso" + "transactions_in_progress": "Transazioni in corso", + "express_fee": "Tassa di espressione", + "invalid_fee": "Inserisci una tariffa valida", + "average_fee": "Commissione media", + "fee_explanation": "Spiegazione delle commissioni:\n1. Le commissioni di input devono essere aggiunte alla commissione base di 0,1 XDAG.\n2. Maggiore è la commissione, maggiore è la velocità di conferma della transazione." } \ No newline at end of file diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 4208a08..55fd997 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -150,5 +150,9 @@ "transactions": "取引", "forget_the_password": "パスワードを忘れた", "time_zone": "タイムゾーン", - "transactions_in_progress": "取引中" + "transactions_in_progress": "取引中", + "express_fee": "可変手数料", + "invalid_fee": "有効な手数料を入力してください", + "average_fee": "平均手数料", + "fee_explanation": "手数料の説明:\n1. 入力手数料は0.1 XDAGの基本手数料に加算されます\n2. 手数料が高いほど、取引の確認速度が速くなります" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 10aea9a..3c23355 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -150,5 +150,9 @@ "transactions": "Транзакции", "forget_the_password": "Забыли пароль", "time_zone": "Часовой пояс", - "transactions_in_progress": "Транзакции в процессе" + "transactions_in_progress": "Транзакции в процессе", + "express_fee": "Вариабельная комиссия", + "invalid_fee": "Пожалуйста, введите действительную сумму сбора", + "average_fee": "Средняя комиссия", + "fee_explanation": "Пояснение к комиссии:\n1. Комиссия за ввод данных должна быть добавлена ​​к базовой комиссии в размере 0,1 XDAG\n2. Чем выше комиссия, тем выше скорость подтверждения транзакции." } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 8545df9..b6ae0c4 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -150,5 +150,10 @@ "transactions": "交易", "forget_the_password": "忘记密码", "time_zone": "时区", - "transactions_in_progress": "交易进行中" + "transactions_in_progress": "交易进行中", + "express_fee": "可变手续费", + "invalid_fee": "请输入有效的手续费", + "average_fee": "平均手续费", + "fee_explanation": "手续费说明:\n1. 输入费用需加上基础费用 0.1 XDAG\n2. 手续费越高,交易确认速度越快" + } \ No newline at end of file diff --git a/lib/page/detail/send_page.dart b/lib/page/detail/send_page.dart index 7490c66..cb7abce 100644 --- a/lib/page/detail/send_page.dart +++ b/lib/page/detail/send_page.dart @@ -43,6 +43,12 @@ class SendPage extends StatefulWidget { class _SendPageState extends State { late TextEditingController controller; late TextEditingController controller2; + late TextEditingController controller3; + + String fee = ""; + bool showFeeBox = false; + String averageFee = "0.10 XDAG"; + bool isLoadingAverageFee = false; String amount = ""; String remark = ""; String error = ""; @@ -56,6 +62,7 @@ class _SendPageState extends State { super.initState(); controller = TextEditingController(); controller2 = TextEditingController(); + controller3 = TextEditingController(); WidgetsBinding.instance.addPostFrameCallback((_) async { SendPageRouteParams args = SendPageRouteParams(address: ''); @@ -68,6 +75,7 @@ class _SendPageState extends State { amount = args.amount; remark = args.remark; }); + fetchAverageFee(); }); } @@ -79,6 +87,46 @@ class _SendPageState extends State { super.dispose(); } + Future fetchAverageFee() async { + setState(() { + isLoadingAverageFee = true; + }); + try { + ConfigModal config = Provider.of(context, listen: false); + String rpcURL = config.getCurrentRpc(); + + Response response = await dio.post( + rpcURL, + cancelToken: cancelToken, + data: { + "jsonrpc": "2.0", + "method": "xdag_getAverageFee", + "params": [], + "id": 1 + }, + ); + if (response.data != null && response.data['result'] != null) { + double feeValue = double.tryParse(response.data['result'].toString()) ?? 0.0; + setState(() { + averageFee = "${feeValue.toStringAsFixed(2)} XDAG"; + }); + } else { + setState(() { + averageFee = "0.10 XDAG"; + }); + } + } catch (e) { + debugPrint("获取平均手续费失败:$e"); + setState(() { + averageFee = "0.10 XDAG"; // 异常时用默认值 + }); + } finally { + setState(() { + isLoadingAverageFee = false; + }); + } + } + static void isolateFunction(SendPort sendPort) async { final receivePort = ReceivePort(); sendPort.send(receivePort.sendPort); @@ -90,9 +138,13 @@ class _SendPageState extends State { String fromAddress = data[3] as String; String remark = data[4] as String; String nonce = data[5] as String; + String fee = data[6] as String; + bool isPrivateKey = res.trim().split(' ').length == 1; bip32.BIP32 wallet = Helper.createWallet(isPrivate: isPrivateKey, content: res); - String result = TransactionHelper.getTransaction(fromAddress, toAddress, remark, double.parse(amount), wallet, nonce); + + String result = TransactionHelper.getTransaction(fromAddress, toAddress, remark, double.parse(amount), wallet, nonce, double.parse(fee)); + sendPort.send(['success', result]); }); } @@ -121,9 +173,11 @@ class _SendPageState extends State { receivePort.listen((data) async { var sendAmount = amount; var sendRemark = remark; + var sendFee = fee; + if (data is SendPort) { var subSendPort = data; - subSendPort.send([res, toAddress, amount, fromAddress, remark, nonce]); + subSendPort.send([res, toAddress, amount, fromAddress, remark, nonce, fee]); } else if (data is List) { String result = data[1]; // print('result: $result'); @@ -143,15 +197,17 @@ class _SendPageState extends State { var res = response.data['result'] as String; // 把内容存在 localstorage,返回列表的时候,从 localstorage 中获取 if (res.length == 32 && res.trim().split(' ').length == 1) { - var transactionItem = Transaction(time: DateTime.now().toIso8601String(), amount: Helper.removeTrailingZeros(sendAmount.toString()), address: fromAddress, status: 'pending', from: fromAddress, to: toAddress, type: 0, hash: '', fee: 0.1, blockAddress: res, remark: sendRemark); + var transactionItem = Transaction(time: DateTime.now().toIso8601String(), amount: Helper.removeTrailingZeros(sendAmount.toString()), address: fromAddress, status: 'pending', from: fromAddress, to: toAddress, type: 0, hash: '', fee: double.parse(sendFee), blockAddress: res, remark: sendRemark); TransactionModal transactionModal = Provider.of(context, listen: false); transactionModal.addTransaction(transactionItem, fromAddress); controller.clear(); controller2.clear(); + controller3.clear(); setState(() { isLoad = false; amount = ''; remark = ''; + fee = ''; }); Helper.changeAndroidStatusBar(true); @@ -191,9 +247,11 @@ class _SendPageState extends State { setState(() { error = res; isLoad = false; + showFeeBox = false; }); controller.clear(); controller2.clear(); + controller3.clear(); } } } on DioException catch (e) { @@ -364,6 +422,114 @@ class _SendPageState extends State { ], ), ), + const SizedBox(height: 15), + Row( + children: [ + Checkbox( + value: showFeeBox, + onChanged: args.isFromScanQR // 扫码场景禁用复选框 + ? null + : (value) { + setState(() { + showFeeBox = value ?? false; + if (!showFeeBox) { + fee = ""; + controller3.clear(); + } + }); + }, + activeColor: DarkColors.mainColor, + checkColor: Colors.white, + ), + Text( + AppLocalizations.of(context)!.express_fee, + style: Helper.fitChineseFont( + context, + const TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + const Spacer(), + Text( + "${AppLocalizations.of(context)!.average_fee}: $averageFee", + style: Helper.fitChineseFont( + context, + const TextStyle( + fontSize: 14, + color: Colors.white54, + ), + ), + ), + ], + ), + if (showFeeBox && !args.isFromScanQR) ...[ + const SizedBox(height: 10), + Container( + constraints: const BoxConstraints(maxHeight: 150), + decoration: BoxDecoration( + color: DarkColors.bgColor, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: DarkColors.mainColor, width: 1), + ), + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AutoSizeTextField( + controller: controller3, + onChanged: (value) { + setState(() => fee = value); + }, + minFontSize: 16, + maxLines: null, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + keyboardAppearance: Brightness.dark, + enabled: !args.isFromScanQR, + style: Helper.fitChineseFont( + context, + const TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}')), + ], + decoration: InputDecoration( + filled: true, + fillColor: DarkColors.blockColor, + hintText: AppLocalizations.of(context)!.fee, + hintStyle: const TextStyle(color: Colors.white54), + border: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + ), + ), + const SizedBox(height: 10), + Text( + AppLocalizations.of(context)!.fee_explanation, + style: Helper.fitChineseFont( + context, + const TextStyle( + fontSize: 14, + color: Colors.white70, + height: 1.5, + ), + ), + ), + ], + ), + ), + ), + ], Text(AppLocalizations.of(context)!.remark, style: Helper.fitChineseFont(context, const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w500))), const SizedBox(height: 15), AutoSizeTextField( @@ -440,6 +606,18 @@ class _SendPageState extends State { }); return; } + if (showFeeBox) { + // 勾选了自定义手续费:必须输入有效数字 + if (fee == '' || fee.isEmpty || double.tryParse(fee) == null) { + setState(() { + error = 'Fee must be greater than 0'; + }); + return; + } + } else { + // 未勾选:fee为0 + fee = '0'; + } if (isLoad) return; setState(() { error = ''; @@ -452,7 +630,7 @@ class _SendPageState extends State { if (context.mounted) { Helper.changeAndroidStatusBar(true); - var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(amount.toString()), address: wallet.address, status: 'pending', from: wallet.address, to: args.address, type: 0, hash: '', fee: 0.1, blockAddress: "", remark: remark); + var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(amount.toString()), address: wallet.address, status: 'pending', from: wallet.address, to: args.address, type: 0, hash: '', fee: double.parse(fee), blockAddress: "", remark: remark); bool? flag = await Helper.showBottomSheet( context, TransactionShowDetail(transaction: transactionItem), diff --git a/lib/page/detail/transaction_page.dart b/lib/page/detail/transaction_page.dart index 4975ea0..1bc2300 100644 --- a/lib/page/detail/transaction_page.dart +++ b/lib/page/detail/transaction_page.dart @@ -71,8 +71,8 @@ class _TransactionPageState extends State { for (var i = 0; i < response.data["block_as_transaction"].length; i++) { var item = response.data["block_as_transaction"][i]; if (item['direction'] == "fee") { - //newFee = item['amount']; - newFee = '0.1'; + newFee = item['amount']; + // newFee = '0.1'; } else { if (isSend) { if (item['direction'] == "output") { @@ -218,7 +218,7 @@ class _TransactionPageState extends State { Helper.showToast(context, AppLocalizations.of(context)!.copied_to_clipboard); }), const SizedBox(height: 1), - TransactionButton(showCopy: false, title: AppLocalizations.of(context)!.fee, value: '$fee XDAG', borderRadius: transaction.remark.isNotEmpty ? BorderRadius.zero : const BorderRadius.only(bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8))), + TransactionButton(showCopy: false, title: AppLocalizations.of(context)!.fee, value: '${transaction.fee + 0.1} XDAG', borderRadius: transaction.remark.isNotEmpty ? BorderRadius.zero : const BorderRadius.only(bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8))), const SizedBox(height: 1), if (transaction.remark.isNotEmpty) TransactionButton( @@ -314,7 +314,7 @@ class TransactionShowDetail extends StatelessWidget { value: transaction.from, ), const SizedBox(height: 1), - TransactionButton(showCopy: false, title: AppLocalizations.of(context)!.fee, value: '${transaction.fee} XDAG', borderRadius: transaction.remark.isNotEmpty ? BorderRadius.zero : const BorderRadius.only(bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8))), + TransactionButton(showCopy: false, title: AppLocalizations.of(context)!.fee, value: '${transaction.fee + 0.1} XDAG', borderRadius: transaction.remark.isNotEmpty ? BorderRadius.zero : const BorderRadius.only(bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8))), const SizedBox(height: 1), if (transaction.remark.isNotEmpty) TransactionButton(