diff --git a/assets/.env.development b/assets/.env.development index 7b55c1cf8f1bb6b9d52450ade04b281e75cd8b60..e729a99dcbdb370ed648598b13648477f65e1143 100644 --- a/assets/.env.development +++ b/assets/.env.development @@ -12,4 +12,5 @@ CARD_COLOR_TEXT=Äž1 Wallet Dev # that are available with the less latency DUNITER_NODES=https://g1.duniter.fr https://g1.le-sou.org https://g1.cgeek.fr https://g1.monnaielibreoccitanie.org https://g1.duniter.fr https://g1.le-sou.org https://g1.cgeek.fr CESIUM_PLUS_NODES=https://g1.data.le-sou.org https://g1.data.e-is.pro https://g1.data.presler.fr https://g1.data.mithril.re +GVA_NODES=https://g1.asycn.io/gva https://g1.librelois.fr/gva https://duniter-g1.p2p.legal/gva # not updated: https://g1.data.adn.life diff --git a/assets/env.production.txt b/assets/env.production.txt index bb71ab38cd29b3df6ae851d532d1e2ba7c26dbe0..236ee33571d9eecdb0aa7d8a8b9198c4b260549a 100644 --- a/assets/env.production.txt +++ b/assets/env.production.txt @@ -11,5 +11,6 @@ CARD_COLOR_TEXT=Äž1 Wallet # that are available with the less latency DUNITER_NODES=https://g1.duniter.fr https://g1.le-sou.org https://g1.cgeek.fr https://g1.monnaielibreoccitanie.org https://g1.duniter.fr https://g1.le-sou.org https://g1.cgeek.fr CESIUM_PLUS_NODES=https://g1.data.le-sou.org https://g1.data.e-is.pro https://g1.data.presler.fr https://g1.data.mithril.re +GVA_NODES=https://g1.asycn.io/gva https://g1.librelois.fr/gva https://duniter-g1.p2p.legal/gva # not updated: https://g1.data.adn.life diff --git a/lib/data/models/app_state.g.dart b/lib/data/models/app_state.g.dart index b3274a5a2844bf007e3a9d0226cc6772f2a2ce0d..dca06c2fc3f6db2daa603fd305b2ad99e6141664 100644 --- a/lib/data/models/app_state.g.dart +++ b/lib/data/models/app_state.g.dart @@ -9,9 +9,11 @@ part of 'app_state.dart'; AppState _$AppStateFromJson(Map<String, dynamic> json) => AppState( introViewed: json['introViewed'] as bool? ?? false, warningViewed: json['warningViewed'] as bool? ?? false, + expertMode: json['expertMode'] as bool? ?? false, ); Map<String, dynamic> _$AppStateToJson(AppState instance) => <String, dynamic>{ 'introViewed': instance.introViewed, 'warningViewed': instance.warningViewed, + 'expertMode': instance.expertMode, }; diff --git a/lib/data/models/node.dart b/lib/data/models/node.dart index 2a0da0742c7d3eef1ca25d2b0b93b7688259fc4b..064a5514712dc778fc77dc6f7b201743c7cd0b1d 100644 --- a/lib/data/models/node.dart +++ b/lib/data/models/node.dart @@ -52,20 +52,4 @@ List<Node> readDotNodeConfig(String entry) => List<Node> defaultDuniterNodes = readDotNodeConfig('DUNITER_NODES'); List<Node> defaultCesiumPlusNodes = readDotNodeConfig('CESIUM_PLUS_NODES'); - -const List<Node> defaultDuniterNodesRemove = <Node>[ - Node(url: 'https://g1.duniter.fr'), - Node(url: 'https://g1.le-sou.org'), - Node(url: 'https://g1.cgeek.fr'), - Node(url: 'https://g1.monnaielibreoccitanie.org'), - Node(url: 'https://g1.duniter.fr'), - Node(url: 'https://g1.le-sou.org'), - Node(url: 'https://g1.cgeek.fr') -]; - -const List<Node> defaultCesiumPlusNodesRemove = <Node>[ - Node(url: 'https://g1.data.e-is.pro'), - Node(url: 'https://g1.data.presler.fr'), - Node(url: 'https://g1.data.le-sou.org'), - Node(url: 'https://g1.data.mithril.re') -]; +List<Node> defaultGvaNodes = readDotNodeConfig('GVA_NODES'); diff --git a/lib/data/models/node_list_cubit.dart b/lib/data/models/node_list_cubit.dart index e19f091b66ba6d1fc43aa01eaef7c455dc938d92..2136aea828f9aef2af092acad789efea76e8c958 100644 --- a/lib/data/models/node_list_cubit.dart +++ b/lib/data/models/node_list_cubit.dart @@ -51,6 +51,8 @@ class NodeListCubit extends HydratedCubit<NodeListState> { List<Node> get cesiumPlusNodes => state.cesiumPlusNodes; + List<Node> get gvaNodes => state.gvaNodes; + @override NodeListState? fromJson(Map<String, dynamic> json) => NodeListState.fromJson(json); diff --git a/lib/data/models/node_list_state.dart b/lib/data/models/node_list_state.dart index b4571ba92b146b33380f32d6591af4bd239a44a3..2c7d480be357b29276b6a6ea7d2afe53efdd74cf 100644 --- a/lib/data/models/node_list_state.dart +++ b/lib/data/models/node_list_state.dart @@ -1,3 +1,4 @@ +import 'package:copy_with_extension/copy_with_extension.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -8,30 +9,25 @@ part 'node_list_state.g.dart'; @immutable @JsonSerializable() +@CopyWith() class NodeListState extends Equatable { - NodeListState({List<Node>? duniterNodes, List<Node>? cesiumPlusNodes}) + NodeListState( + {List<Node>? duniterNodes, + List<Node>? cesiumPlusNodes, + List<Node>? gvaNodes}) : duniterNodes = duniterNodes ?? defaultDuniterNodes, - cesiumPlusNodes = cesiumPlusNodes ?? defaultCesiumPlusNodes; + cesiumPlusNodes = cesiumPlusNodes ?? defaultCesiumPlusNodes, + gvaNodes = gvaNodes ?? defaultGvaNodes; factory NodeListState.fromJson(Map<String, dynamic> json) => _$NodeListStateFromJson(json); final List<Node> duniterNodes; final List<Node> cesiumPlusNodes; - - NodeListState copyWith( - {List<Node>? duniterNodes, - List<Node>? cesiumPlusNodes, - DateTime? lastFetchDuniterNodesTime, - DateTime? lastFetchCPlusNodesTime}) { - return NodeListState( - duniterNodes: duniterNodes ?? this.duniterNodes, - cesiumPlusNodes: cesiumPlusNodes ?? this.cesiumPlusNodes, - ); - } + final List<Node> gvaNodes; @override - List<Object?> get props => <Object>[duniterNodes, cesiumPlusNodes]; + List<Object?> get props => <Object>[duniterNodes, cesiumPlusNodes, gvaNodes]; Map<String, dynamic> toJson() => _$NodeListStateToJson(this); } diff --git a/lib/data/models/node_list_state.g.dart b/lib/data/models/node_list_state.g.dart index cfb09d8dd6327c913d5eda5bcddf0b5d195cb137..0cc3c51959e0bac1ef0051c8b895bb540352747e 100644 --- a/lib/data/models/node_list_state.g.dart +++ b/lib/data/models/node_list_state.g.dart @@ -2,6 +2,83 @@ part of 'node_list_state.dart'; +// ************************************************************************** +// CopyWithGenerator +// ************************************************************************** + +abstract class _$NodeListStateCWProxy { + NodeListState duniterNodes(List<Node>? duniterNodes); + + NodeListState cesiumPlusNodes(List<Node>? cesiumPlusNodes); + + NodeListState gvaNodes(List<Node>? gvaNodes); + + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `NodeListState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. + /// + /// Usage + /// ```dart + /// NodeListState(...).copyWith(id: 12, name: "My name") + /// ```` + NodeListState call({ + List<Node>? duniterNodes, + List<Node>? cesiumPlusNodes, + List<Node>? gvaNodes, + }); +} + +/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfNodeListState.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfNodeListState.copyWith.fieldName(...)` +class _$NodeListStateCWProxyImpl implements _$NodeListStateCWProxy { + const _$NodeListStateCWProxyImpl(this._value); + + final NodeListState _value; + + @override + NodeListState duniterNodes(List<Node>? duniterNodes) => + this(duniterNodes: duniterNodes); + + @override + NodeListState cesiumPlusNodes(List<Node>? cesiumPlusNodes) => + this(cesiumPlusNodes: cesiumPlusNodes); + + @override + NodeListState gvaNodes(List<Node>? gvaNodes) => this(gvaNodes: gvaNodes); + + @override + + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `NodeListState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. + /// + /// Usage + /// ```dart + /// NodeListState(...).copyWith(id: 12, name: "My name") + /// ```` + NodeListState call({ + Object? duniterNodes = const $CopyWithPlaceholder(), + Object? cesiumPlusNodes = const $CopyWithPlaceholder(), + Object? gvaNodes = const $CopyWithPlaceholder(), + }) { + return NodeListState( + duniterNodes: duniterNodes == const $CopyWithPlaceholder() + ? _value.duniterNodes + // ignore: cast_nullable_to_non_nullable + : duniterNodes as List<Node>?, + cesiumPlusNodes: cesiumPlusNodes == const $CopyWithPlaceholder() + ? _value.cesiumPlusNodes + // ignore: cast_nullable_to_non_nullable + : cesiumPlusNodes as List<Node>?, + gvaNodes: gvaNodes == const $CopyWithPlaceholder() + ? _value.gvaNodes + // ignore: cast_nullable_to_non_nullable + : gvaNodes as List<Node>?, + ); + } +} + +extension $NodeListStateCopyWith on NodeListState { + /// Returns a callable class that can be used as follows: `instanceOfNodeListState.copyWith(...)` or like so:`instanceOfNodeListState.copyWith.fieldName(...)`. + // ignore: library_private_types_in_public_api + _$NodeListStateCWProxy get copyWith => _$NodeListStateCWProxyImpl(this); +} + // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** @@ -14,10 +91,14 @@ NodeListState _$NodeListStateFromJson(Map<String, dynamic> json) => cesiumPlusNodes: (json['cesiumPlusNodes'] as List<dynamic>?) ?.map((e) => Node.fromJson(e as Map<String, dynamic>)) .toList(), + gvaNodes: (json['gvaNodes'] as List<dynamic>?) + ?.map((e) => Node.fromJson(e as Map<String, dynamic>)) + .toList(), ); Map<String, dynamic> _$NodeListStateToJson(NodeListState instance) => <String, dynamic>{ 'duniterNodes': instance.duniterNodes, 'cesiumPlusNodes': instance.cesiumPlusNodes, + 'gvaNodes': instance.gvaNodes, }; diff --git a/lib/data/models/node_manager.dart b/lib/data/models/node_manager.dart index 2c0a3355f38ef0e11df8fb2b21ecd6e5150b9fd1..ccf9e3825fe565ca6878c31e7f584eaf38e153e3 100644 --- a/lib/data/models/node_manager.dart +++ b/lib/data/models/node_manager.dart @@ -2,11 +2,7 @@ import 'package:flutter/foundation.dart'; import 'node.dart'; import 'node_list_cubit.dart'; - -enum NodeType { - duniter, - cesiumPlus, -} +import 'node_type.dart'; class NodeManager { factory NodeManager() { @@ -23,13 +19,16 @@ class NodeManager { final List<Node> duniterNodes = <Node>[]; final List<Node> cesiumPlusNodes = <Node>[]; + final List<Node> gvaNodes = <Node>[]; void loadFromCubit(NodeListCubit cubit) { NodeManagerObserver.instance.cubit = cubit; duniterNodes.clear(); cesiumPlusNodes.clear(); + gvaNodes.clear(); duniterNodes.addAll(cubit.duniterNodes); cesiumPlusNodes.addAll(cubit.cesiumPlusNodes); + gvaNodes.addAll(cubit.gvaNodes); } void updateNodes(NodeType type, List<Node> newNodes) { @@ -41,8 +40,11 @@ class NodeManager { List<Node> nodeList(NodeType type) => _getList(type); - List<Node> _getList(NodeType type) => - type == NodeType.duniter ? duniterNodes : cesiumPlusNodes; + List<Node> _getList(NodeType type) => type == NodeType.duniter + ? duniterNodes + : type == NodeType.cesiumPlus + ? cesiumPlusNodes + : gvaNodes; void addNode(NodeType type, Node node) { final List<Node> nodes = _getList(type); diff --git a/lib/g1/api.dart b/lib/g1/api.dart index d69338ee81a72f300efbf2141968e64d39d19864..8ec9389a72f5d07d43186eb6e4edeb1670dbae88 100644 --- a/lib/g1/api.dart +++ b/lib/g1/api.dart @@ -1,4 +1,5 @@ import 'dart:convert'; + // import 'dart:developer' as developer; import 'dart:io'; @@ -9,6 +10,7 @@ import 'package:http/http.dart'; import '../data/models/contact.dart'; import '../data/models/node.dart'; import '../data/models/node_manager.dart'; +import '../data/models/node_type.dart'; import '../main.dart'; import 'g1_helper.dart'; @@ -139,19 +141,19 @@ Future<void> fetchDuniterNodes({bool force = false}) async { .difference(NodeManager().lastDuniterFetchNodesTime) .compareTo(Duration(minutes: minutesToWait)) > 0 || */ - duniterNodesWorking() < NodeManager.maxNodes) { + nodesWorking(type) < NodeManager.maxNodes) { if (force) { NodeManager().updateNodes(type, defaultDuniterNodes); logger('Fetching nodes forced'); } else { logger( - 'Fetching nodes as we did it more than ${minutesToWait}min ago and we have only ${duniterNodesWorking()}'); + 'Fetching nodes as we did it more than ${minutesToWait}min ago and we have only ${nodesWorking(type)}'); } final List<Node> nodes = await _fetchDuniterNodesFromPeers(); NodeManager().updateNodes(type, nodes); } else { logger( - 'Skipping to fetch nodes as we already did it less than ${minutesToWait}min ago and we have ${duniterNodesWorking()}'); + 'Skipping to fetch nodes as we already did it less than ${minutesToWait}min ago and we have ${nodesWorking(type)}'); if (!kReleaseMode) { // developer.log(StackTrace.current.toString()); } @@ -166,20 +168,26 @@ Future<void> fetchCesiumPlusNodes({bool force = false}) async { NodeManager().updateNodes(type, defaultCesiumPlusNodes); logger('Fetching cesium nodes forced'); } else { - logger('Fetching cesium plus nodes, we have ${cesiumPlusNodesWorking()}'); + logger('Fetching cesium plus nodes, we have ${nodesWorking(type)}'); } - final List<Node> nodes = await _fetchCesiumPlusNodes(); + final List<Node> nodes = await _fetchNodes(NodeType.cesiumPlus); NodeManager().updateNodes(type, nodes); } -int duniterNodesWorking() => NodeManager() - .nodeList(NodeType.duniter) - .where((Node n) => n.errors < NodeManager.maxNodeErrors) - .toList() - .length; +Future<void> fetchGvaNodes({bool force = false}) async { + const NodeType type = NodeType.gva; + if (force) { + NodeManager().updateNodes(type, defaultGvaNodes); + logger('Fetching ${type.name} nodes forced'); + } else { + logger('Fetching ${type.name} nodes, we have ${nodesWorking(type)}'); + } + final List<Node> nodes = await _fetchNodes(NodeType.gva); + NodeManager().updateNodes(type, nodes); +} -int cesiumPlusNodesWorking() => NodeManager() - .nodeList(NodeType.cesiumPlus) +int nodesWorking(NodeType type) => NodeManager() + .nodeList(type) .where((Node n) => n.errors < NodeManager.maxNodeErrors) .toList() .length; @@ -259,12 +267,11 @@ Future<List<Node>> _fetchDuniterNodesFromPeers() async { return lNodes; } -Future<List<Node>> _fetchCesiumPlusNodes() async { +Future<List<Node>> _fetchNodes(NodeType type) async { final List<Node> lNodes = <Node>[]; String? fastestNode; late Duration fastestLatency = const Duration(minutes: 1); try { - const NodeType type = NodeType.cesiumPlus; final List<Node> currentNodes = <Node>[...NodeManager().nodeList(type)]; currentNodes.shuffle(); for (final Node node in currentNodes) { @@ -278,7 +285,7 @@ Future<List<Node>> _fetchCesiumPlusNodes() async { fastestNode = endpoint; fastestLatency = latency; if (!kReleaseMode) { - logger('Node bloc: Current faster node $fastestNode'); + logger('Node $type: Current faster node $fastestNode'); } NodeManager().insertNode(type, node); lNodes.insert(0, node); @@ -293,9 +300,9 @@ Future<List<Node>> _fetchCesiumPlusNodes() async { } logger( - 'Fetched ${lNodes.length} cesium plus nodes ordered by latency (first: ${lNodes.first.url})'); + 'Fetched ${lNodes.length} ${type.name} nodes ordered by latency (first: ${lNodes.first.url})'); } catch (e, stacktrace) { - logger('General error in fetch cplus nodes: $e'); + logger('General error in fetch ${type.name}: $e'); logger(stacktrace); } lNodes.sort((Node a, Node b) => a.latency.compareTo(b.latency)); @@ -309,8 +316,13 @@ Future<Duration> _pingNode(String node, NodeType type) async { await http .get(Uri.parse(type == NodeType.duniter ? '$node/network/peers/self/ping' - // see: http://g1.data.e-is.pro/network/peering - : '$node/network/peering')) + : type == NodeType.cesiumPlus + ? + // see: http://g1.data.e-is.pro/network/peering + '$node/network/peering' + : + // gva (just the url) + node)) // Decrease http timeout during ping .timeout(const Duration(seconds: 10)); stopwatch.stop(); @@ -337,13 +349,20 @@ Future<http.Response> requestCPlusWithRetry(String path, return _requestWithRetry(NodeType.cesiumPlus, path, true, retryWith404); } +Future<http.Response> requestGvaWithRetry(String path, + {bool retryWith404 = true}) async { + return _requestWithRetry(NodeType.gva, path, true, retryWith404); +} + Future<http.Response> _requestWithRetry( NodeType type, String path, bool dontRecord, bool retryWith404) async { final List<Node> nodes = NodeManager().nodeList(type); if (nodes.isEmpty) { nodes.addAll(type == NodeType.duniter ? defaultDuniterNodes - : defaultCesiumPlusNodes); + : type == NodeType.cesiumPlus + ? defaultCesiumPlusNodes + : defaultGvaNodes); } for (int i = 0; i < nodes.length; i++) { final Node node = nodes[i]; diff --git a/lib/main.dart b/lib/main.dart index e6d68e3ad725fd88f8effc41a006ef839f73e828..fa5c62e980bdcd5dd2f531e5113cf09d67df1abe 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,6 +26,7 @@ import 'data/models/contact_cubit.dart'; import 'data/models/node_list_cubit.dart'; import 'data/models/node_list_state.dart'; import 'data/models/node_manager.dart'; +import 'data/models/node_type.dart'; import 'data/models/payment_cubit.dart'; import 'data/models/transaction_cubit.dart'; import 'g1/api.dart'; @@ -209,6 +210,7 @@ class _GinkgoAppState extends State<GinkgoApp> { _printNodeStatus(); await fetchDuniterNodes(); await fetchCesiumPlusNodes(); + await fetchGvaNodes(); _printNodeStatus(prefix: 'Continuing'); } @@ -216,8 +218,9 @@ class _GinkgoAppState extends State<GinkgoApp> { final int nDuniterNodes = NodeManager().nodeList(NodeType.duniter).length; final int nCesiumPlusNodes = NodeManager().nodeList(NodeType.cesiumPlus).length; + final int nGvaNodes = NodeManager().nodeList(NodeType.gva).length; logger( - '$prefix with $nDuniterNodes duniter nodes and $nCesiumPlusNodes c+ nodes'); + '$prefix with $nDuniterNodes duniter nodes, $nCesiumPlusNodes c+ nodes, and $nGvaNodes gva nodes'); if (!kReleaseMode) { logger('${NodeManager().nodeList(NodeType.cesiumPlus)}'); } diff --git a/lib/ui/screens/fifth_screen.dart b/lib/ui/screens/fifth_screen.dart index 96d4ff49baca08876eea01562843735cef3f86e1..d729f39b98056e09db1599236dfbe511497badaf 100644 --- a/lib/ui/screens/fifth_screen.dart +++ b/lib/ui/screens/fifth_screen.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../data/models/app_cubit.dart'; import '../../data/models/app_state.dart'; -import '../../data/models/node_manager.dart'; +import '../../data/models/node_type.dart'; import '../ui_helpers.dart'; import '../widgets/bottom_widget.dart'; import '../widgets/fifth_screen/export_dialog.dart'; @@ -74,6 +74,8 @@ class FifthScreen extends StatelessWidget { const NodeInfoCard(type: NodeType.duniter), if (state.expertMode) const NodeInfoCard(type: NodeType.cesiumPlus), + if (state.expertMode) + const NodeInfoCard(type: NodeType.gva), if (state.expertMode) LinkCard( title: 'code_card_title', diff --git a/lib/ui/widgets/fifth_screen/node_info.dart b/lib/ui/widgets/fifth_screen/node_info.dart index bd0e1d37fd383ff121c0d9b5497edd50e83bb3a3..216ea35bdf931cde6e4908446289533445912252 100644 --- a/lib/ui/widgets/fifth_screen/node_info.dart +++ b/lib/ui/widgets/fifth_screen/node_info.dart @@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../data/models/node.dart'; import '../../../data/models/node_list_cubit.dart'; import '../../../data/models/node_list_state.dart'; -import '../../../data/models/node_manager.dart'; +import '../../../data/models/node_type.dart'; import '../../../g1/api.dart'; import '../../../main.dart'; import '../fifth_screen/info_card.dart'; @@ -19,8 +19,11 @@ class NodeInfoCard extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder<NodeListCubit, NodeListState>( builder: (BuildContext nodeContext, NodeListState state) { - final List<Node> nodes = - type == NodeType.duniter ? state.duniterNodes : state.cesiumPlusNodes; + final List<Node> nodes = type == NodeType.duniter + ? state.duniterNodes + : type == NodeType.cesiumPlus + ? state.cesiumPlusNodes + : state.gvaNodes; return GestureDetector( onTap: () => ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -31,8 +34,11 @@ class NodeInfoCard extends StatelessWidget { logger('On long press'); if (type == NodeType.duniter) { fetchDuniterNodes(force: true); - } else { + } + if (type == NodeType.cesiumPlus) { fetchCesiumPlusNodes(force: true); + } else { + fetchGvaNodes(force: true); } ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(tr('reloading_nodes', @@ -49,7 +55,11 @@ class NodeInfoCard extends StatelessWidget { ? tr('using_nodes_first', namedArgs: <String, String>{'node': nodes.first.url}) : '', - icon: type == NodeType.duniter ? Icons.hub : Icons.diversity_2)); + icon: type == NodeType.duniter + ? Icons.hub + : type == NodeType.cesiumPlus + ? Icons.diversity_2 + : Icons.g_mobiledata)); }); } }