Skip to content
Snippets Groups Projects
Commit fffcd5f4 authored by vjrj's avatar vjrj
Browse files

Third hackaton

parent fd93bb25
No related branches found
No related tags found
No related merge requests found
Showing
with 419 additions and 69 deletions
......@@ -14,7 +14,6 @@ users to manage their Ğ1 currency on their mobile device using just a browser.
amounts (which lightweight wallets will understand).
* Internationalization (i18n)
* Some contextual help (for example, by tapping on "Validity").
* Connectivity detection (to retry transactions)
* QR code reader
## Work in progress
......@@ -23,6 +22,7 @@ users to manage their Ğ1 currency on their mobile device using just a browser.
* Send and receive Ğ1 transactions
* View transaction history
* View Ğ1 balance and currency conversion rate
* Connectivity detection (to retry transactions, and other net operations)
## Demo
......
NET=https://g1.duniter.org/
# Sentry is not used right now in development
SENTRY_DSN=https://306345cb87ee4e1cbbe9023fb4afc5fc@sentry.comunes.org/6
# Card customization
CARD_COLOR_LEFT=0xFF598040
CARD_COLOR_RIGHT=0xFF225500
# Empty for default
CARD_COLOR_TEXT=Ğ1 Wallet Dev
# Nodes space-separated
# The duniter nodes are only used at boot time, later it tries to calculate periodically the nodes
# 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.e-is.pro https://g1.data.presler.fr https://g1.data.le-sou.org https://g1.data.mithril.r
NET=https://g1.duniter.org/
SENTRY_DSN=https://306345cb87ee4e1cbbe9023fb4afc5fc@sentry.comunes.org/6
# Card customization
CARD_COLOR_LEFT=0xFF05112B
CARD_COLOR_RIGHT=0xFF085476
# Empty for default
CARD_COLOR_TEXT=Ğ1 Wallet Cop
# Nodes space-separated
# The duniter nodes are only used at boot time, later it tries to calculate periodically the nodes
# 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.e-is.pro https://g1.data.presler.fr https://g1.data.le-sou.org https://g1.data.mithril.r
assets/img/favicon.png

11.9 KiB | W: | H:

assets/img/favicon.png

11.9 KiB | W: | H:

assets/img/favicon.png
assets/img/favicon.png
assets/img/favicon.png
assets/img/favicon.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -9,6 +9,7 @@ convert leaf.png -resize 256x256 \
convert leaf.png -resize 256x256 favicon.png
cp favicon.png ../../web/
cp favicon.ico ../../web/
for i in 192 512; do convert leaf.png -resize $ix$i ../../web/icons/Icon-$i.png; done
for i in 192 512; do convert leaf.png -resize $ix$i ../../web/icons/Icon-maskable-$i.png; done
......@@ -8,7 +8,7 @@
"bottom_nav_trd": "Contacts",
"bottom_nav_frd": "Balance",
"bottom_nav_fifth": "Info",
"title_first": "Send Ğ1",
"send_g1": "Send Ğ1",
"g1_amount": "Amount to send",
"g1_amount_hint": "Amount to send in Ğ1",
"g1_form_pay_send": "Send",
......@@ -32,22 +32,34 @@
"skip": "Skip",
"start": "Start",
"offline": "You are Offline!",
"online-terminal": "Online",
"offline-terminal": "Offline",
"show-qr-to-client": "Show your public key to your client",
"show-qr-to-client-amount": "Show your QR with this amount",
"keys-tooltip": "Public and private keys in Ğ1 and Duniter are like a lock and key system, where the public key acts as the lock that can be opened by anyone with the corresponding private key, providing a secure way to authenticate and verify transactions",
"card-validity": "Validity",
"card-validity-tooltip": "Please note that this wallet is only accessible while using this specific browser and device. If you delete or reset the browser, you will lose access to this wallet and the funds stored in it.",
"demo-desc": "Please refrain from using this with real transactions for now.",
"connected-to": "We are connected to node:",
"export-key": "Export your wallet",
"import-key": "Import your wallet",
"copy-your-key": "Copy your public key",
"key-copied-to-clipboard": "Your public key has been copied to the clipboard",
"online_terminal": "Online",
"offline_terminal": "Offline",
"show_qr_to_client": "Show your public key to your client",
"show_qr_to_client_amount": "Show your QR with this amount",
"keys_tooltip": "Public and private keys in Ğ1 and Duniter are like a lock and key system, where the public key acts as the lock that can be opened by anyone with the corresponding private key, providing a secure way to authenticate and verify transactions",
"card_validity": "Validity",
"card_validity_tooltip": "Please note that this wallet is only accessible while using this specific browser and device. If you delete or reset the browser, you will lose access to this wallet and the funds stored in it.",
"demo_desc": "Please refrain from using this with real transactions for now.",
"connected_to": "We are connected to node:",
"export_key": "Export your wallet",
"import_key": "Import your wallet",
"copy_your_key": "Copy your public key",
"key_copied_to_clipboard": "Your public key has been copied to the clipboard",
"some_key_copied_to_clipboard": "The public key has been copied to the clipboard",
"key_tools_title": "Keys and Tools",
"transactions": "Transactions",
"balance": "Balance",
"transaction_from_to": "From {from} to {to}",
"your_wallet": "your wallet"
"your_wallet": "your wallet",
"delete_contact": "Delete contact",
"search_contacts": "Search contacts",
"no_contacts": "You don't have contacts yet",
"data_load_error": "Error loading data",
"add_contact": "Add contact",
"contact_added": "Contact added",
"current_nodes_length": "(of {nodes})",
"no_transactions": "You don't have any transaction yet",
"qr-scanner-title": "Scan the QR of someone",
"copy_contact_key": "Copy",
"nothing_found": "Nothing found"
}
......@@ -8,7 +8,7 @@
"bottom_nav_trd": "Contactos",
"bottom_nav_frd": "Saldo",
"bottom_nav_fifth": "Información",
"title_first": "Enviar Ğ1",
"send_g1": "Enviar Ğ1",
"g1_amount": "Monto a enviar",
"g1_amount_hint": "Monto a enviar en Ğ1",
"g1_form_pay_send": "Enviar",
......@@ -32,22 +32,33 @@
"skip": "Omitir",
"start": "Comenzar",
"offline": "¡Está desconectad@!",
"online-terminal": "En línea",
"offline-terminal": "Fuera de línea",
"show-qr-to-client": "Muestre su clave pública a su clientæ",
"show-qr-to-client-amount": "Muestre su QR con esa cantidad",
"keys-tooltip": "Las claves públicas y privadas en Ğ1 y Duniter son como un sistema de cerradura y llave, donde la clave pública actúa como la cerradura que puede ser abierta por cualquiera que tenga la clave privada correspondiente, proporcionando una forma segura de autenticar y verificar transacciones.",
"card-validity": "Validez",
"card-validity-tooltip": "Tenga en cuenta que este monedero solo es accesible mientras utiliza este navegador y este dispositivo específico. Si borra o restablece el navegador, perderá el acceso a este monedero y los fondos almacenados en el.",
"demo-desc": "Por favor, no utilice esto aún para transacciones reales.",
"connected-to": "Estamos conectados al nodo:",
"export-key": "Exporta tu monedero",
"import-key": "Importa tu monedero",
"copy-your-key": "Copia tu clave pública",
"key-copied-to-clipboard": "Tu clave pública se ha copiado al portapapeles",
"online_terminal": "En línea",
"offline_terminal": "Fuera de línea",
"show_qr_to_client": "Muestre su clave pública a su clientæ",
"show_qr_to_client_amount": "Muestre su QR con esa cantidad",
"keys_tooltip": "Las claves públicas y privadas en Ğ1 y Duniter son como un sistema de cerradura y llave, donde la clave pública actúa como la cerradura que puede ser abierta por cualquiera que tenga la clave privada correspondiente, proporcionando una forma segura de autenticar y verificar transacciones.",
"card_validity": "Validez",
"card_validity_tooltip": "Tenga en cuenta que este monedero solo es accesible mientras utiliza este navegador y este dispositivo específico. Si borra o restablece el navegador, perderá el acceso a este monedero y los fondos almacenados en el.",
"demo_desc": "Por favor, no utilice esto aún para transacciones reales.",
"connected_to": "Estamos conectados al nodo:",
"export_key": "Exporta tu monedero",
"import_key": "Importa tu monedero",
"copy_your_key": "Copia tu clave pública",
"key_copied_to_clipboard": "Tu clave pública se ha copiado al portapapeles",
"some_key_copied_to_clipboard": "La clave pública se ha copiado al portapapeles",
"key_tools_title": "Llaves y Herramientas",
"transactions": "Transacciones",
"balance": "Balance",
"transaction_from_to": "Desde {from} a {to}",
"your_wallet": "tu monedero"
"your_wallet": "tu monedero",
"delete_contact": "Borrar contacto",
"search_contacts": "Buscar contactos",
"no_contacts": "No tiene contactos aún",
"data_load_error": "Error al cargar los datos",
"add_contact": "Añadir contacto",
"contact_added": "Contacto añadido",
"no_transactions": "No tiene todavía ninguna transacción",
"qr-scanner-title": "Escanea el QR de alguien",
"copy_contact_key": "Copiar",
"nothing_found": "No se ha encontrado nada"
}
......@@ -8,7 +8,7 @@
"bottom_nav_trd": "Contacts",
"bottom_nav_frd": "Solde",
"bottom_nav_fifth": "Info",
"title_first": "Envoyer des Ğ1",
"send_g1: "Envoyer des Ğ1",
"g1_amount": "Montant à envoyer",
"g1_amount_hint": "Montant à envoyer en Ğ1",
"g1_form_pay_send": "Envoyer",
......@@ -32,22 +32,26 @@
"skip": "Passer",
"start": "Commencer",
"offline": "Vous êtes hors ligne !",
"online-terminal": "En ligne",
"offline-terminal": "Hors ligne",
"show-qr-to-client": "Montrez votre clé publique à votre client",
"show-qr-to-client-amount": "Montrez votre QR avec ce montant",
"keys-tooltip": "Les clés publiques et privées en Ğ1 et Duniter fonctionnent comme un système de verrou et de clé, où la clé publique agit comme le verrou qui peut être ouvert par n'importe qui ayant la clé privée correspondante, offrant ainsi un moyen sécurisé d'authentifier et de vérifier les transactions",
"card-validity": "Validité",
"card-validity-tooltip": "Veuillez noter que ce portefeuille n'est accessible que lors de l'utilisation de ce navigateur et de cet appareil spécifiques. Si vous supprimez ou réinitialisez le navigateur, vous perdrez l'accès à ce portefeuille et aux fonds qu'il contient.",
"demo-desc": "Veuillez vous abstenir d'utiliser ceci avec de vraies transactions pour le moment.",
"connected-to": "Nous sommes connectés au nœud:",
"export-key": "Exporter votre portefeuille",
"import-key": "Importer votre portefeuille",
"copy-your-key": "Copier votre clé publique",
"key-copied-to-clipboard": "Votre clé publique a été copiée dans le presse-papiers",
"online_terminal": "En ligne",
"offline_terminal": "Hors ligne",
"show_qr_to_client": "Montrez votre clé publique à votre client",
"show_qr_to_client_amount": "Montrez votre QR avec ce montant",
"keys_tooltip": "Les clés publiques et privées en Ğ1 et Duniter fonctionnent comme un système de verrou et de clé, la clé publique agit comme le verrou qui peut être ouvert par n'importe qui ayant la clé privée correspondante, offrant ainsi un moyen sécurisé d'authentifier et de vérifier les transactions",
"card_validity": "Validité",
"card_validity_tooltip": "Veuillez noter que ce portefeuille n'est accessible que lors de l'utilisation de ce navigateur et de cet appareil spécifiques. Si vous supprimez ou réinitialisez le navigateur, vous perdrez l'accès à ce portefeuille et aux fonds qu'il contient.",
"demo_desc": "Veuillez vous abstenir d'utiliser ceci avec de vraies transactions pour le moment.",
"connected_to": "Nous sommes connectés au nœud:",
"export_key": "Exporter votre portefeuille",
"import_key": "Importer votre portefeuille",
"copy_your_key": "Copier votre clé publique",
"key_copied_to_clipboard": "Votre clé publique a été copiée dans le presse-papiers",
"key_tools_title": "Clés et outils",
"transactions": "Transactions",
"balance": "Solde",
"transaction_from_to": "De {from} à {to}",
"your_wallet": "votre portefeuille"
"your_wallet": "votre portefeuille",
"delete_contact": "Supprimer le contact",
"search_contacts": "Rechercher des contacts",
"no_contacts": "Vous n'avez pas encore de contacts",
"data_load_error": "Erreur de chargement des données"
}
......@@ -26,6 +26,4 @@ class AppCubit extends HydratedCubit<AppState> {
Map<String, dynamic> toJson(AppState state) {
return state.toJson();
}
}
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'isJsonSerializable.dart';
import 'is_json_serializable.dart';
part 'app_state.g.dart';
......
import 'dart:typed_data';
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'is_json_serializable.dart';
part 'contact.g.dart';
@JsonSerializable()
@CopyWith()
class Contact extends Equatable implements IsJsonSerializable<Contact> {
const Contact({
this.nick,
required this.pubkey,
this.avatar,
this.notes,
this.name,
});
factory Contact.fromJson(Map<String, dynamic> json) =>
_$ContactFromJson(json);
final String? nick;
final String pubkey;
@JsonKey(fromJson: _fromList, toJson: _toList)
final Uint8List? avatar;
final String? notes;
final String? name;
@override
List<Object?> get props => <dynamic>[nick, pubkey, avatar, notes, name];
@override
Map<String, dynamic> toJson() => _$ContactToJson(this);
@override
Contact fromJson(Map<String, dynamic> json) => Contact.fromJson(json);
static Uint8List _fromList(List<int> list) => Uint8List.fromList(list);
static List<int> _toList(Uint8List? uint8List) =>
uint8List != null ? uint8List.toList() : <int>[];
@override
String toString() {
return 'Contact $pubkey, hasAvatar: ${avatar != null}, nick: $nick, name: $name';
}
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'contact.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$ContactCWProxy {
Contact nick(String? nick);
Contact pubkey(String pubkey);
Contact avatar(Uint8List? avatar);
Contact notes(String? notes);
Contact name(String? name);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `Contact(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// Contact(...).copyWith(id: 12, name: "My name")
/// ````
Contact call({
String? nick,
String? pubkey,
Uint8List? avatar,
String? notes,
String? name,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfContact.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfContact.copyWith.fieldName(...)`
class _$ContactCWProxyImpl implements _$ContactCWProxy {
const _$ContactCWProxyImpl(this._value);
final Contact _value;
@override
Contact nick(String? nick) => this(nick: nick);
@override
Contact pubkey(String pubkey) => this(pubkey: pubkey);
@override
Contact avatar(Uint8List? avatar) => this(avatar: avatar);
@override
Contact notes(String? notes) => this(notes: notes);
@override
Contact name(String? name) => this(name: name);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `Contact(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// Contact(...).copyWith(id: 12, name: "My name")
/// ````
Contact call({
Object? nick = const $CopyWithPlaceholder(),
Object? pubkey = const $CopyWithPlaceholder(),
Object? avatar = const $CopyWithPlaceholder(),
Object? notes = const $CopyWithPlaceholder(),
Object? name = const $CopyWithPlaceholder(),
}) {
return Contact(
nick: nick == const $CopyWithPlaceholder()
? _value.nick
// ignore: cast_nullable_to_non_nullable
: nick as String?,
pubkey: pubkey == const $CopyWithPlaceholder() || pubkey == null
? _value.pubkey
// ignore: cast_nullable_to_non_nullable
: pubkey as String,
avatar: avatar == const $CopyWithPlaceholder()
? _value.avatar
// ignore: cast_nullable_to_non_nullable
: avatar as Uint8List?,
notes: notes == const $CopyWithPlaceholder()
? _value.notes
// ignore: cast_nullable_to_non_nullable
: notes as String?,
name: name == const $CopyWithPlaceholder()
? _value.name
// ignore: cast_nullable_to_non_nullable
: name as String?,
);
}
}
extension $ContactCopyWith on Contact {
/// Returns a callable class that can be used as follows: `instanceOfContact.copyWith(...)` or like so:`instanceOfContact.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$ContactCWProxy get copyWith => _$ContactCWProxyImpl(this);
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Contact _$ContactFromJson(Map<String, dynamic> json) => Contact(
nick: json['nick'] as String?,
pubkey: json['pubkey'] as String,
avatar: Contact._fromList(json['avatar'] as List<int>),
notes: json['notes'] as String?,
name: json['name'] as String?,
);
Map<String, dynamic> _$ContactToJson(Contact instance) => <String, dynamic>{
'nick': instance.nick,
'pubkey': instance.pubkey,
'avatar': Contact._toList(instance.avatar),
'notes': instance.notes,
'name': instance.name,
};
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'contact.dart';
import 'contact_state.dart';
class ContactsCubit extends HydratedCubit<ContactsState> {
ContactsCubit() : super(const ContactsState());
void addContact(Contact contact) {
if (!state.contacts.contains(contact)) {
emit(state.copyWith(contacts: <Contact>[...state.contacts, contact]));
}
}
void removeContact(Contact contact) {
final List<Contact> contactsTruncated = state.contacts
.where((Contact c) => c.pubkey != contact.pubkey)
.toList();
final List<Contact> filteredContactsTruncated = state.filteredContacts
.where((Contact c) => c.pubkey != contact.pubkey)
.toList();
emit(state.copyWith(
contacts: contactsTruncated,
filteredContacts: filteredContactsTruncated));
}
void updateContact(Contact contact) {
final List<Contact> contacts = state.contacts.map((Contact c) {
if (c.pubkey == contact.pubkey) {
return contact;
}
return c;
}).toList();
emit(state.copyWith(contacts: contacts));
}
void resetFilter() {
emit(state.copyWith(filteredContacts: state.contacts));
}
void filterContacts(String query) {
final List<Contact> contacts = state.contacts.where((Contact c) {
if (c.pubkey.contains(query)) {
return true;
}
if (c.nick != null && c.nick!.contains(query)) {
return true;
}
if (c.name != null && c.name!.contains(query)) {
return true;
}
if (c.notes != null && c.notes!.contains(query)) {
return true;
}
return false;
}).toList();
emit(state.copyWith(filteredContacts: contacts));
}
List<Contact> get contacts => state.contacts;
List<Contact> get filteredContacts => state.filteredContacts;
@override
ContactsState fromJson(Map<String, dynamic> json) {
final List<dynamic> contactsJson = json['contacts'] as List<dynamic>;
final List<Contact> contacts = contactsJson
.map((dynamic c) => Contact.fromJson(c as Map<String, dynamic>))
.toList();
return ContactsState(contacts: contacts);
}
@override
Map<String, dynamic> toJson(ContactsState state) {
final List<Map<String, dynamic>> contactsJson =
state.contacts.map((Contact c) => c.toJson()).toList();
return <String, dynamic>{'contacts': contactsJson};
}
@override
String get id => 'contacts';
bool isContact(String pubKey) =>
state.contacts.any((Contact c) => c.pubkey == pubKey);
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'contact.dart';
@immutable
class ContactsState extends Equatable {
const ContactsState(
{this.contacts = const <Contact>[],
this.filteredContacts = const <Contact>[]});
final List<Contact> contacts;
final List<Contact> filteredContacts;
@override
List<Object?> get props => <Object>[contacts, filteredContacts];
ContactsState copyWith(
{List<Contact>? contacts, List<Contact>? filteredContacts}) {
return ContactsState(
contacts: contacts ?? this.contacts,
filteredContacts: filteredContacts ?? this.filteredContacts);
}
@override
String toString() {
return 'ContactsState(contacts: $contacts, filteredContacts: $filteredContacts)';
}
}
import 'dart:typed_data';
Uint8List uIntFromList(List<int> list) => Uint8List.fromList(list);
List<int> uIntToList(Uint8List? uInt8List) =>
uInt8List != null ? uInt8List.toList() : <int>[];
import 'package:equatable/equatable.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:json_annotation/json_annotation.dart';
import 'isJsonSerializable.dart';
import 'is_json_serializable.dart';
part 'node.g.dart';
......@@ -46,7 +47,13 @@ class Node extends Equatable implements IsJsonSerializable<Node> {
List<Object?> get props => <dynamic>[url, latency, errors];
}
const List<Node> defaultDuniterNodes = <Node>[
List<Node> readDotNodeConfig(String entry) =>
dotenv.env[entry]!.split(' ').map((String url) => Node(url: url)).toList();
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'),
......@@ -56,7 +63,7 @@ const List<Node> defaultDuniterNodes = <Node>[
Node(url: 'https://g1.cgeek.fr')
];
const List<Node> defaultCesiumPlusNodes = <Node>[
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'),
......
import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'node.dart';
......@@ -6,9 +7,11 @@ import 'node_list_state.dart';
class NodeListCubit extends HydratedCubit<NodeListState> {
NodeListCubit() : super(NodeListState());
static int maxNodes = kReleaseMode ? 20 : 5;
static int maxNodeErrors = 3;
void addDuniterNode(Node node) {
final Node? nFound = _find(node);
if (nFound == null) {
if (!_find(node)) {
// Does not exists, so add it
emit(state.copyWith(duniterNodes: <Node>[...state.duniterNodes, node]));
} else {
......@@ -17,12 +20,10 @@ class NodeListCubit extends HydratedCubit<NodeListState> {
}
}
Node? _find(Node node) =>
state.duniterNodes.firstWhere((Node n) => n.url == node.url);
bool _find(Node node) => state.duniterNodes.contains(node);
void insertDuniterNode(Node node) {
final Node? nFound = _find(node);
if (nFound == null) {
if (!_find(node)) {
emit(state.copyWith(duniterNodes: <Node>[node, ...state.duniterNodes]));
} else {
// it exists
......
......@@ -10,10 +10,12 @@ part 'node_list_state.g.dart';
@JsonSerializable()
class NodeListState extends Equatable {
NodeListState(
{this.duniterNodes = defaultDuniterNodes,
this.cesiumPlusNodes = defaultCesiumPlusNodes,
{List<Node>? duniterNodes,
List<Node>? cesiumPlusNodes,
DateTime? lastFetchNodesTime})
: lastFetchNodesTime = lastFetchNodesTime ?? DateTime(1970);
: duniterNodes = duniterNodes ?? defaultDuniterNodes,
cesiumPlusNodes = cesiumPlusNodes ?? defaultCesiumPlusNodes,
lastFetchNodesTime = lastFetchNodesTime ?? DateTime(1970);
factory NodeListState.fromJson(Map<String, dynamic> json) =>
_$NodeListStateFromJson(json);
......
......@@ -9,13 +9,11 @@ part of 'node_list_state.dart';
NodeListState _$NodeListStateFromJson(Map<String, dynamic> json) =>
NodeListState(
duniterNodes: (json['duniterNodes'] as List<dynamic>?)
?.map((e) => Node.fromJson(e as Map<String, dynamic>))
.toList() ??
defaultDuniterNodes,
?.map((e) => Node.fromJson(e as Map<String, dynamic>))
.toList(),
cesiumPlusNodes: (json['cesiumPlusNodes'] as List<dynamic>?)
?.map((e) => Node.fromJson(e as Map<String, dynamic>))
.toList() ??
defaultCesiumPlusNodes,
?.map((e) => Node.fromJson(e as Map<String, dynamic>))
.toList(),
lastFetchNodesTime: json['lastFetchNodesTime'] == null
? null
: DateTime.parse(json['lastFetchNodesTime'] as String),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment