diff --git a/assets/translations/en.json b/assets/translations/en.json index ee956da60df3256e3185a0198b2e7a1a252910f9..0d758bfda1b2494473ccf5c0d7f77d32c1ae8e83 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -15,8 +15,8 @@ "search_user_title": "User to pay", "search_user": "Search (user or public key)", "search_user_btn": "Search user", - "g1_form_pay_desc": "Description", - "g1_form_pay_hint": "Enter a description (optional)", + "g1_form_pay_desc": "Comment", + "g1_form_pay_hint": "Enter a comment (optional)", "code_card_title": "Code repository", "intro_1_title": "Welcome to our Ğ1 wallet!", "intro_1_description": "With this wallet, you can easily and securely store, send, and receive Ğ1 currency (also known as 'June').", diff --git a/assets/translations/es.json b/assets/translations/es.json index 22954d9bbeb2b351d8dddd18d457200ab42e3e28..78fd028e14eed32a63521df23423332bab1d4df6 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -15,8 +15,8 @@ "search_user_title": "Usuario a pagar", "search_user": "Buscar (usuari@ o clave pública)", "search_user_btn": "Buscar usuari@", - "g1_form_pay_desc": "Descripción", - "g1_form_pay_hint": "Introduzca una descripción (opcional)", + "g1_form_pay_desc": "Comment", + "g1_form_pay_hint": "Introduzca un comentario (opcional)", "code_card_title": "Repositorio de código", "intro_1_title": "¡Bienvenido a nuestro monedero Ğ1!", "intro_1_description": "Con este monedero, puede almacenar, enviar y recibir fácil y seguramente la moneda Ğ1 (también conocida como 'Juna').", diff --git a/lib/data/models/payment_cubit.dart b/lib/data/models/payment_cubit.dart index fc9169247a8fc274707f6bab56fb95ae3642aea9..71d7be532cf8a5b04b44bfa8c3b032cb7f2b1f16 100644 --- a/lib/data/models/payment_cubit.dart +++ b/lib/data/models/payment_cubit.dart @@ -35,9 +35,7 @@ class PaymentCubit extends HydratedCubit<PaymentState> { void selectKey(String publicKey) { final PaymentState newState = PaymentState( - publicKey: publicKey, - amount: state.amount, - description: state.description); + publicKey: publicKey, amount: state.amount, comment: state.comment); emit(newState); } @@ -51,4 +49,8 @@ class PaymentCubit extends HydratedCubit<PaymentState> { void clearRecipient() { emit(PaymentState.emptyPayment); } + + void selectAmount(double amount) { + emit(state.copyWith(amount: amount)); + } } diff --git a/lib/data/models/payment_state.dart b/lib/data/models/payment_state.dart index 7d5572075a1993238959345f34ccf70d4410ac86..9e2d70f89501e09b8de11c6f170b58a38b6778e9 100644 --- a/lib/data/models/payment_state.dart +++ b/lib/data/models/payment_state.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; +import '../../g1/g1_helper.dart'; import 'model_utils.dart'; part 'payment_state.g.dart'; @@ -13,7 +14,7 @@ class PaymentState extends Equatable { required this.publicKey, this.nick, this.avatar, - this.description = '', + this.comment = '', this.amount, this.isSent = false, }); @@ -21,11 +22,14 @@ class PaymentState extends Equatable { factory PaymentState.fromJson(Map<String, dynamic> json) => _$PaymentStateFromJson(json); + bool canBeSent() => + !isSent && validateKey(publicKey) && amount != null && amount! > 0; + final String publicKey; final String? nick; @JsonKey(fromJson: uIntFromList, toJson: uIntToList) final Uint8List? avatar; - final String description; + final String comment; final double? amount; final bool isSent; @@ -43,7 +47,7 @@ class PaymentState extends Equatable { publicKey: publicKey ?? this.publicKey, nick: nick ?? this.nick, avatar: avatar ?? this.avatar, - description: description ?? this.description, + comment: description ?? this.comment, amount: amount ?? this.amount, isSent: isSent ?? this.isSent, ); @@ -61,5 +65,5 @@ class PaymentState extends Equatable { @override List<Object?> get props => - <dynamic>[publicKey, nick, avatar, description, amount, isSent]; + <dynamic>[publicKey, nick, avatar, comment, amount, isSent]; } diff --git a/lib/data/models/payment_state.g.dart b/lib/data/models/payment_state.g.dart index c8dd3a3723bf82fbe0ba9878cc75e76d0750fcb1..7fbad50ebad28e3eed69b698d530355a4f7075ae 100644 --- a/lib/data/models/payment_state.g.dart +++ b/lib/data/models/payment_state.g.dart @@ -10,7 +10,7 @@ PaymentState _$PaymentStateFromJson(Map<String, dynamic> json) => PaymentState( publicKey: json['publicKey'] as String, nick: json['nick'] as String?, avatar: uIntFromList(json['avatar'] as List<int>), - description: json['description'] as String? ?? '', + comment: json['comment'] as String? ?? '', amount: (json['amount'] as num?)?.toDouble(), isSent: json['isSent'] as bool? ?? false, ); @@ -20,7 +20,7 @@ Map<String, dynamic> _$PaymentStateToJson(PaymentState instance) => 'publicKey': instance.publicKey, 'nick': instance.nick, 'avatar': uIntToList(instance.avatar), - 'description': instance.description, + 'comment': instance.comment, 'amount': instance.amount, 'isSent': instance.isSent, }; diff --git a/lib/g1/api.dart b/lib/g1/api.dart index 8ec9389a72f5d07d43186eb6e4edeb1670dbae88..4da703701f2a73b8ae2e91dfdd14c41b7a81b53f 100644 --- a/lib/g1/api.dart +++ b/lib/g1/api.dart @@ -1,8 +1,8 @@ import 'dart:convert'; - // import 'dart:developer' as developer; import 'dart:io'; +import 'package:durt/durt.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:http/http.dart'; @@ -12,6 +12,7 @@ import '../data/models/node.dart'; import '../data/models/node_manager.dart'; import '../data/models/node_type.dart'; import '../main.dart'; +import '../shared_prefs.dart'; import 'g1_helper.dart'; // Tx history @@ -192,6 +193,11 @@ int nodesWorking(NodeType type) => NodeManager() .toList() .length; +List<Node> nodesWorkingList(NodeType type) => NodeManager() + .nodeList(type) + .where((Node n) => n.errors < NodeManager.maxNodeErrors) + .toList(); + Future<List<Node>> _fetchDuniterNodesFromPeers() async { final List<Node> lNodes = <Node>[]; // To compare with something... @@ -415,3 +421,21 @@ Future<http.Response> _requestWithRetry( throw Exception( 'Cannot make the request to any of the ${nodes.length} nodes'); } + +Future<String> pay( + {required String to, required double amount, String? comment}) async { + final List<Node> nodes = nodesWorkingList(NodeType.gva); + // reorder list to use others + if (nodes.isNotEmpty) { + nodes.shuffle(); + final Gva gva = Gva(node: nodes.first.url); + final CesiumWallet wallet = await SharedPreferencesHelper().getWallet(); + final String response = await gva.pay( + recipient: to, + amount: amount, + comment: comment ?? '', + cesiumSeed: wallet.seed); + return response; + } + return 'Sorry: I cannot find a working node to send the transaction'; +} diff --git a/lib/ui/screens/g1_textfield.dart b/lib/ui/screens/g1_textfield.dart index 18d663784f3875318cda5eb5494b79ee6d420705..051a75a645aa53f34e24dc438e76f0a327f03614 100644 --- a/lib/ui/screens/g1_textfield.dart +++ b/lib/ui/screens/g1_textfield.dart @@ -1,32 +1,40 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import '../../data/models/payment_cubit.dart'; +import '../../data/models/payment_state.dart'; + class G1PayAmountField extends StatelessWidget { const G1PayAmountField({super.key, required this.controller}); final TextEditingController controller; @override - Widget build(BuildContext context) { - return TextField( - controller: controller, - keyboardType: const TextInputType.numberWithOptions(decimal: true), - decoration: InputDecoration( - labelText: tr('g1_amount'), - hintText: tr('g1_amount_hint'), - prefixIcon: Padding( - padding: const EdgeInsets.all(10.0), - child: SvgPicture.asset( - colorFilter: - ColorFilter.mode(Colors.purple.shade600, BlendMode.srcIn), - 'assets/img/gbrevedot.svg', - width: 20.0, - height: 20.0, - ), - ), - border: const OutlineInputBorder(), - ), - ); - } + Widget build(BuildContext context) => BlocBuilder<PaymentCubit, PaymentState>( + builder: (BuildContext context, PaymentState state) => TextField( + controller: controller, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + onChanged: (String? value) { + if (value != null) { + context.read<PaymentCubit>().selectAmount(double.parse(value)); + } + }, + decoration: InputDecoration( + labelText: tr('g1_amount'), + hintText: tr('g1_amount_hint'), + prefixIcon: Padding( + padding: const EdgeInsets.all(10.0), + child: SvgPicture.asset( + colorFilter: + ColorFilter.mode(Colors.purple.shade600, BlendMode.srcIn), + 'assets/img/gbrevedot.svg', + width: 20.0, + height: 20.0, + ), + ), + border: const OutlineInputBorder(), + ), + )); } diff --git a/lib/ui/screens/pay_form.dart b/lib/ui/screens/pay_form.dart index deb72e17f1359eef6999dd6b64913010d600e998..8dfaf78d61f30b2080c5eea0faa3fd16b131fb3e 100644 --- a/lib/ui/screens/pay_form.dart +++ b/lib/ui/screens/pay_form.dart @@ -4,6 +4,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../data/models/payment_cubit.dart'; import '../../data/models/payment_state.dart'; +import '../../data/models/transaction_cubit.dart'; +import '../../g1/api.dart'; +import '../ui_helpers.dart'; import 'g1_textfield.dart'; class PayForm extends StatefulWidget { @@ -16,7 +19,7 @@ class PayForm extends StatefulWidget { class _PayFormState extends State<PayForm> { final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final TextEditingController _amountController = TextEditingController(); - final TextEditingController _descController = TextEditingController(); + final TextEditingController _commentController = TextEditingController(); @override Widget build(BuildContext context) { @@ -31,7 +34,7 @@ class _PayFormState extends State<PayForm> { G1PayAmountField(controller: _amountController), const SizedBox(height: 10.0), TextField( - controller: _descController, + controller: _commentController, decoration: InputDecoration( labelText: tr('g1_form_pay_desc'), hintText: tr('g1_form_pay_hint'), @@ -41,14 +44,21 @@ class _PayFormState extends State<PayForm> { ), const SizedBox(height: 10.0), ElevatedButton( - onPressed: - null /* () { - if (_formKey.currentState != null && - _formKey.currentState!.validate()) { - // Enviar formulario - } - }, */ - , + onPressed: !state.canBeSent() && + state.amount != null && + _weHaveBalance(context, state.amount!) + ? () {} + : () async { + final String response = await pay( + to: state.publicKey, + comment: state.comment, + amount: state.amount!); + if (!mounted) { + // Cannot show a tooltip if the widget is not now visible + return; + } + showTooltip(context, '', response); + }, child: Text(tr('g1_form_pay_send')), ), ], @@ -56,4 +66,7 @@ class _PayFormState extends State<PayForm> { ); }); } + + bool _weHaveBalance(BuildContext context, double amount) => + context.read<TransactionsCubit>().balance > amount; }