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

Wallet and terminal card working

parent f7734ce4
No related branches found
No related tags found
No related merge requests found
Showing
with 3148 additions and 226 deletions
...@@ -32,7 +32,6 @@ ...@@ -32,7 +32,6 @@
/build/ /build/
# Web related # Web related
lib/generated_plugin_registrant.dart
# Symbolication related # Symbolication related
app.*.symbols app.*.symbols
...@@ -44,3 +43,4 @@ app.*.map.json ...@@ -44,3 +43,4 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
web
...@@ -146,6 +146,7 @@ following code: ...@@ -146,6 +146,7 @@ following code:
- G1 logo - G1 logo
- undraw intro images: https://undraw.co/license - undraw intro images: https://undraw.co/license
- Chipcard https://commons.wikimedia.org/wiki/File:Chipcard.svg under the Creative Commons Attribution-Share Alike 3.0 Unported license.
Thanks! Thanks!
......
NET=https://g1-test.duniter.org/ NET=https://g1.duniter.org/
assets/img/chip.png

15.2 KiB

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="37.679146mm"
height="37.679146mm"
viewBox="0 0 37.679146 37.679146"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="chip.svg"
inkscape:export-filename="/home/vjrj/dev/mondamono/assets/img/chip.png"
inkscape:export-xdpi="146.98"
inkscape:export-ydpi="146.98"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="false"
inkscape:zoom="0.64052329"
inkscape:cx="133.48461"
inkscape:cy="333.32121"
inkscape:window-width="3440"
inkscape:window-height="1370"
inkscape:window-x="0"
inkscape:window-y="33"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient3804">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3806" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3808" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3804"
id="linearGradient3810"
x1="-132.07143"
y1="492.36218"
x2="663.5"
y2="492.36218"
gradientUnits="userSpaceOnUse" />
</defs>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-69.613169,-60.284089)">
<path
id="rect3929"
d="M 178.53786,95.203693 H 376.85185 V 293.51768 H 178.53786 Z"
style="fill:none;stroke:none;stroke-width:0.264583" />
<g
id="g810"
transform="translate(-142.47377,-106.24506)">
<g
transform="matrix(0.16513438,0,0,0.16513438,212.07688,87.986943)"
id="g3791">
<path
inkscape:connector-curvature="0"
style="fill:#edd400;stroke:#c4a000;stroke-width:12;stroke-miterlimit:4"
d="M 33.335034,481.62601 H 194.95943 c 15.10987,0 27.27412,12.16425 27.27412,27.27412 v 161.6244 c 0,15.10986 -12.16425,27.27412 -27.27412,27.27412 H 33.335034 c -15.109862,0 -27.2741185,-12.16426 -27.2741185,-27.27412 v -161.6244 c 0,-15.10987 12.1642565,-27.27412 27.2741185,-27.27412 z"
id="rect2996" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 112.12693,689.71742 V 628.09811"
id="path3767"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 111.11678,487.68691 v 84.85281 H 76.771593 v 60.60916 h 66.670067 v -59.599 l 23.23351,-23.23351 h 48.48732"
id="path3773"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 75.761441,572.53972 48.190106,556.62141 H 11.111678"
id="path3775"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 74.751288,599.81384 H 12.121831"
id="path3777"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 75.761441,632.13872 54.043161,653.857 H 10.101525"
id="path3779"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 142.43151,633.14888 20.70813,20.70812 h 51.0127"
id="path3781"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 143.44166,598.80369 h 71.72083"
id="path3785"
inkscape:connector-curvature="0" />
<path
id="path3787"
d="M 36.992859,486.51835 H 191.3016 c 14.42595,0 26.03961,11.61366 26.03961,26.03961 V 666.8667 c 0,14.42594 -11.61366,26.03961 -26.03961,26.03961 H 36.992859 c -14.42594,0 -26.039602,-11.61367 -26.039602,-26.03961 V 512.55796 c 0,-14.42595 11.613662,-26.03961 26.039602,-26.03961 z"
style="fill:none;stroke:#2e3436;stroke-width:6.68316;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>
assets/img/favicon.ico

142 KiB

#!/bin/bash
optipng gbrevedot.png
convert gbrevedot.png -resize 256x256 \
-define icon:auto-resize="256,128,96,64,48,32,16" \
favicon.ico
for i in 192 512; do convert gbrevedot.png -resize $ix$i ../../web/icons/Icon-$i.png; done
for i in 192 512; do convert gbrevedot.png -resize $ix$i ../../web/icons/Icon-maskable-$i.png; done
assets/img/gbrevedot.png

6.25 KiB

assets/img/gbrevedot_alt.png

8.7 KiB

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="512"
height="512"
viewBox="-0.72 -0.72 1.44 1.44"
version="1.1"
id="svg7"
sodipodi:docname="gbrevedot_alt.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs11" />
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
showgrid="false"
inkscape:zoom="1.4042969"
inkscape:cx="256.35605"
inkscape:cy="256"
inkscape:window-width="3440"
inkscape:window-height="1370"
inkscape:window-x="0"
inkscape:window-y="33"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<path
id="g"
d="M 0.176775,0.176775 A 0.25,0.25 0 1 1 0.2165,-0.125 L 0.433025,-0.25 A 0.5,0.5 0 1 0 0.35355,0.35355 l 0.0884,0.0884 V 0.0884 H 0.0884 Z"
fill="#000000"
style="stroke-width:0.25;fill:#cccccc" />
<path
id="breve"
d="m -0.09916917,-0.70930232 -0.09916917,0.0760952 a 0.25000001,0.25000001 0 0 0 0.39667658,7.69e-6 l -0.09917292,-0.076104 a 0.125,0.125 0 0 1 -0.19833449,1.11e-6 z"
fill="#000000"
style="stroke-width:0.125;fill:#cccccc" />
<circle
cx="0"
cy="0.625"
r="0.088399999"
fill="#000000"
id="circle4"
style="stroke-width:0.25;fill:#cccccc" />
</svg>
This diff is collapsed.
{
"app_name": "Flutter Production Boilerplate",
"bottom_nav_first": "Home",
"bottom_nav_second": "Info",
"localization_title": "Lokalisierung",
"localization_content": "Vereinfachte Übersetzungen mit dem Paket Easy Translations.",
"linting_title": "Linting",
"linting_content": "Strengere Linting-Regeln mit Flutter Lints, empfohlen von Google.",
"storage_title": "Speicher",
"storage_content": "Extrem schnelle, in reinem Dart geschriebene Datenbank mit Hive.",
"dark_mode_title": "Nachtmodus",
"dark_mode_content": "Sanfter Übergang in den Nachtmodus durch die eingebaute Theming-Engine.",
"state_title": "Zustand",
"state_content": "Leistungsstarke Zustandsverwaltung mit Cubit & BLOC.",
"display_title": "Anzeige",
"display_content": "Unterstützt Displays mit hoher Bildwiederholrate durch Flutter Displaymode.",
"language_switch_title": "Deutsch verwenden",
"github_card_title": "GitHub repository",
"website_card_title": "Webseite",
"twitter_card_title": "Twitter",
"instagram_card_title": "Instagram",
"donate_card_title": "Spenden",
"author_divider_title": "Autor",
"packages_divider_title": "Pakete"
}
\ No newline at end of file
{ {
"app_name": "MondaMono", "app_name": "Ğinkgo",
"credit_card_title": "Pay with Ğ1", "credit_card_title": "Pay with Ğ1",
"g1_wallet": "Ğ1 Wallet", "g1_wallet": "Ğ1 Wallet",
"close": "CLOSE", "close": "CLOSE",
...@@ -28,34 +28,18 @@ ...@@ -28,34 +28,18 @@
"intro_4_description": "We'll help you create a Ğ1 wallet so you can easily and securely store your Ğ1 currency on your device and send it to others without intermediaries.", "intro_4_description": "We'll help you create a Ğ1 wallet so you can easily and securely store your Ğ1 currency on your device and send it to others without intermediaries.",
"intro_5_title": "And then...", "intro_5_title": "And then...",
"intro_5_description": "From here, you can offer your services and products and receive payment in Ğ1 currency, as well as use Ğ1 currency to purchase goods.", "intro_5_description": "From here, you can offer your services and products and receive payment in Ğ1 currency, as well as use Ğ1 currency to purchase goods.",
"localization_title": "Localization", "intro_6_title": "In order to follow",
"localization_content": "Simplified translations with the Easy Translations package.", "intro_6_description": "Please, draw all the screen to generate your wallet.",
"linting_title": "Linting", "no_internet": "No internet connection",
"linting_content": "Stricter linting rules with flutter lints recommended by the Dart team.", "skip": "Skip",
"storage_title": "Storage", "start": "Start",
"storage_content": "Blazing fast key-value database written in pure Dart with Hive.", "offline": "You are Offline!",
"dark_mode_title": "Dark Mode", "online-terminal": "Online",
"dark_mode_content": "Smooth transition to dark mode by using the inbuilt theming engine.", "offline-terminal": "Offline",
"state_title": "State", "show-qr-to-client": "Show this QR to your client",
"state_content": "Powerful state management using Cubit & BLOC.", "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",
"display_title": "Display", "card-validity": "Validity",
"display_content": "Support for high refresh rate displays with the flutter displaymode package.", "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.",
"language_switch_title": "Use german", "demo-title": "This is a demo",
"website_card_title": "Website", "demo-desc": "Please refrain from using this with real transactions for now."
"twitter_card_title": "Twitter",
"instagram_card_title": "Instagram",
"donate_card_title": "Donate",
"author_divider_title": "Author",
"packages_divider_title": "Packages",
"flutter_bloc": "Flutter bloc",
"bloc": "Bloc",
"hydrated_bloc": "Hydrated bloc",
"equatable": "Equatable",
"lints": "Flutter Lints",
"path_provider": "Path provider",
"flutter_displaymode": "Displaymode",
"easy_localization": "Easy localization",
"hive": "Hive",
"url_launcher": "Url launcher",
"ionicons": "Ionicons"
} }
{
"app_name": "Ğinkgo",
"credit_card_title": "Pagar con Ğ1",
"g1_wallet": "Monedero Ğ1",
"close": "CERRAR",
"bottom_nav_first": "Pagar",
"bottom_nav_second": "Recibir",
"bottom_nav_trd": "Contactos",
"bottom_nav_frd": "Saldo",
"bottom_nav_fifth": "Información",
"title_first": "Enviar Ĝ1",
"g1_amount": "Monto a enviar",
"g1_amount_hint": "Monto a enviar en Ĝ1",
"g1_form_pay_send": "Enviar",
"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)",
"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').",
"intro_2_title": "Una moneda digital creada por la gente, para la gente",
"intro_2_description": "La moneda Ğ1 no depende de ningún gobierno o corporación, es eco-amigable (debido a su bajo consumo de energía), transparente y justa para tod@s.",
"intro_3_title": "La moneda Ğ1 funciona en la red Duniter",
"intro_3_description": "Duniter es una red de criptomonedas descentralizada que permite a las personas crear su propia moneda Ğ1.",
"intro_4_title": "¡Empecemos!",
"intro_4_description": "Le ayudaremos a crear un monedero Ğ1 para que pueda almacenar su moneda Ğ1 de manera fácil y segura en su dispositivo y enviarla a otr@s sin intermediari@s.",
"intro_5_title": "Y luego...",
"intro_5_description": "Desde aquí, puede ofrecer sus servicios y productos y recibir pago en moneda Ğ1, así como usar la moneda Ğ1 para comprar bienes.",
"intro_6_title": "Para continuar",
"intro_6_description": "Por favor, deslice la pantalla hacia abajo para generar su monedero.",
"no_internet": "Sin conexión a internet",
"skip": "Omitir",
"start": "Comenzar",
"offline": "¡Está desconectad@!",
"online-terminal": "En línea",
"offline-terminal": "Fuera de línea",
"show-qr-to-client": "Muestre este código QR a su clientæ",
"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-title": "Esto es una demostración",
"demo-desc": "Por favor, no utilice esto aún para transacciones reales."
}
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import '../main.dart';
class DuniterNodeManager {
DuniterNodeManager() {
_loadNodes();
_startResetErrorsTimer();
}
final String _peerListUrl = 'https://nodes.duniter.org/network/peers';
List<String> _nodes = <String>[];
int _currentNodeIndex = 0;
final int _retryCount = 3;
Map<String, int> _nodeErrors = <String, int>{};
Timer? _resetErrorsTimer;
String? _fastestNode;
late Duration? _fastestLatency;
Future<dynamic> makeRequest(String endpoint) async {
Response response;
for (int i = 0; i < _nodes.length; i++) {
final String currentNode = _nodes[_currentNodeIndex];
try {
response = await http.get(Uri.parse('$currentNode$endpoint'));
if (response.statusCode == 200) {
_resetNodeErrors(currentNode);
return jsonDecode(response.body);
}
} catch (e) {
_incrementNodeErrors(currentNode);
logger('Error: $e');
}
_rotateNodes();
}
throw Exception('No nodes available');
}
Future<void> _loadNodes() async {
try {
final Response response = await http.get(Uri.parse(_peerListUrl));
if (response.statusCode == 200) {
final List<dynamic> peerList =
jsonDecode(response.body) as List<dynamic>;
_nodes = peerList
.where((dynamic peer) =>
(peer as Map<String, dynamic>)['currency'] == 'g1')
.map((dynamic peer) =>
'http://${(peer as Map<String, dynamic>)['host']}:${peer['port']}/')
.toList();
_resetNodeErrors(null);
}
} catch (e) {
logger('Error: $e');
}
}
void _rotateNodes() {
_currentNodeIndex = (_currentNodeIndex + 1) % _nodes.length;
}
void _incrementNodeErrors(String nodeUrl) {
_nodeErrors[nodeUrl] = (_nodeErrors[nodeUrl] ?? 0) + 1;
}
void _resetNodeErrors(String? nodeUrl) {
if (nodeUrl == null) {
_nodeErrors = <String, int>{for (String v in _nodes) v[0]: 0};
} else {
_nodeErrors[nodeUrl] = 0;
}
}
void _startResetErrorsTimer() {
_resetErrorsTimer = Timer.periodic(const Duration(minutes: 5), (_) {
_resetNodeErrors(null);
});
}
void _cancelResetErrorsTimer() {
_resetErrorsTimer?.cancel();
}
bool _hasNodeExceededRetryLimit(String nodeUrl) {
return _nodeErrors[nodeUrl] != null && _nodeErrors[nodeUrl]! >= _retryCount;
}
Future<void> dispose() async {
_cancelResetErrorsTimer();
}
Future<String?> getFastestNode() async {
for (final String node in _nodes) {
final Duration latency = await pingNode(node);
if (_fastestNode == null || latency < _fastestLatency!) {
_fastestNode = node;
_fastestLatency = latency;
}
}
return _fastestNode;
}
Future<Duration> pingNode(String node) async {
try {
final Stopwatch stopwatch = Stopwatch()..start();
await http.get(Uri.parse('$node/network/peers/self/ping'));
stopwatch.stop();
return stopwatch.elapsed;
} catch (e) {
// Handle exception when node is unavailable
return const Duration(days: 20);
}
}
}
// ignore_for_file: unused_local_variable import 'dart:convert';
import 'dart:io' show Platform;
import 'dart:math';
import 'dart:typed_data';
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; import 'package:durt/durt.dart';
import 'package:ed25519_edwards/ed25519_edwards.dart';
void generate() { Random createRandom() {
// Generar un nuevo par de claves Ed25519 if (Platform.isIOS || Platform.isAndroid) {
final KeyPair keyPair = ed.generateKey(); final String osVersion = Platform.operatingSystemVersion;
// Obtener la clave pública y privada como cadenas de bytes final int currentYear = DateTime.now().year;
final PublicKey publicKeyBytes = keyPair.publicKey; final int osYear = int.parse(osVersion.split('.')[0]);
final PrivateKey privateKeyBytes = keyPair.privateKey; final bool isOldDevice = currentYear - osYear >= 5;
final String publicKeyHex = publicKeyBytes.bytes if (isOldDevice) {
.map((int b) => b.toRadixString(16).padLeft(2, '0')) return Random();
.join(); } else {
final String privateKeyHex = privateKeyBytes.bytes try {
.map((int b) => b.toRadixString(16).padLeft(2, '0')) return Random.secure();
.join(); } catch (e) {
return Random();
}
}
} else {
return Random.secure();
}
}
Uint8List generateUintSeed() {
final Random random = createRandom();
return Uint8List.fromList(List<int>.generate(32, (_) => random.nextInt(256)));
}
String seedToString(Uint8List seed) {
final Uint8List seedsBytes = Uint8List.fromList(seed);
final String encoded = json.encode(seedsBytes.toList());
return encoded;
}
CesiumWallet generateCesiumWallet(Uint8List seed) {
return CesiumWallet.fromSeed(seed);
}
Uint8List seedFromString(String sString) {
final List<dynamic> list = json.decode(sString) as List<dynamic>;
final Uint8List bytes =
Uint8List.fromList(list.map((dynamic e) => e as int).toList());
return bytes;
}
/* print("Clave pública: $publicKeyHex"); String generateSalt(int length) {
print("Clave privada: $privateKeyHex"); */ final Random random = createRandom();
const String charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
return List<String>.generate(
length, (int index) => charset[random.nextInt(charset.length)]).join();
} }
import 'dart:io'; import 'dart:io';
import 'package:connectivity_wrapper/connectivity_wrapper.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:easy_logger/easy_logger.dart'; import 'package:easy_logger/easy_logger.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -11,14 +12,16 @@ import 'package:hive_flutter/hive_flutter.dart'; ...@@ -11,14 +12,16 @@ import 'package:hive_flutter/hive_flutter.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:introduction_screen/introduction_screen.dart'; import 'package:introduction_screen/introduction_screen.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:responsive_framework/responsive_wrapper.dart';
import 'config/theme.dart'; import 'config/theme.dart';
import 'cubit/theme_cubit.dart'; import 'cubit/theme_cubit.dart';
import 'shared_prefs.dart';
import 'ui/screens/skeleton_screen.dart'; import 'ui/screens/skeleton_screen.dart';
// logs // logs
final EasyLogger logger = EasyLogger( final EasyLogger logger = EasyLogger(
name: tr('app_name'), name: 'ginkgo',
defaultLevel: LevelMessages.debug, defaultLevel: LevelMessages.debug,
enableBuildModes: <BuildMode>[ enableBuildModes: <BuildMode>[
BuildMode.debug, BuildMode.debug,
...@@ -42,6 +45,11 @@ void main() async { ...@@ -42,6 +45,11 @@ void main() async {
await FlutterDisplayMode.setHighRefreshRate(); await FlutterDisplayMode.setHighRefreshRate();
} }
final SharedPreferencesHelper shared = SharedPreferencesHelper();
await shared.init();
await shared.getWallet();
assert(shared.getPubKey() != null);
// .env // .env
await dotenv.load( await dotenv.load(
fileName: kReleaseMode fileName: kReleaseMode
...@@ -64,7 +72,7 @@ void main() async { ...@@ -64,7 +72,7 @@ void main() async {
path: 'assets/translations', path: 'assets/translations',
supportedLocales: const <Locale>[ supportedLocales: const <Locale>[
Locale('en'), Locale('en'),
Locale('de'), Locale('es'),
], ],
fallbackLocale: const Locale('en'), fallbackLocale: const Locale('en'),
useFallbackTranslations: true, useFallbackTranslations: true,
...@@ -85,9 +93,9 @@ class _AppIntro extends State<AppIntro> { ...@@ -85,9 +93,9 @@ class _AppIntro extends State<AppIntro> {
GlobalKey<IntroductionScreenState>(); GlobalKey<IntroductionScreenState>();
void _onIntroEnd(BuildContext context) { void _onIntroEnd(BuildContext context) {
// Navegar a la pantalla de inicio de la aplicación después de que se complete la introducción
Navigator.of(context).pushReplacement( Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const SkeletonScreen()), MaterialPageRoute<void>(
builder: (BuildContext _) => const SkeletonScreen()),
); );
} }
...@@ -98,18 +106,17 @@ class _AppIntro extends State<AppIntro> { ...@@ -98,18 +106,17 @@ class _AppIntro extends State<AppIntro> {
pages: <PageViewModel>[ pages: <PageViewModel>[
for (int i = 1; i <= 5; i++) for (int i = 1; i <= 5; i++)
createPageViewModel('intro_${i}_title', 'intro_${i}_description', createPageViewModel('intro_${i}_title', 'intro_${i}_description',
'assets/img/undraw_intro_$i.png') 'assets/img/undraw_intro_$i.png'),
], ],
onDone: () => _onIntroEnd(context), onDone: () => _onIntroEnd(context),
showSkipButton: true, showSkipButton: true,
skipOrBackFlex: 0, skipOrBackFlex: 0,
onSkip: () => _onIntroEnd(context),
nextFlex: 0, nextFlex: 0,
// FIXME skip: Text(tr('skip')),
skip: const Text('Saltar'),
next: const Icon(Icons.arrow_forward), next: const Icon(Icons.arrow_forward),
// FIXME done: Text(tr('start'),
done: style: const TextStyle(fontWeight: FontWeight.w600)),
const Text('Empezar', style: TextStyle(fontWeight: FontWeight.w600)),
dotsDecorator: const DotsDecorator( dotsDecorator: const DotsDecorator(
size: Size(10.0, 10.0), size: Size(10.0, 10.0),
color: Color(0xFFBDBDBD), color: Color(0xFFBDBDBD),
...@@ -137,18 +144,26 @@ PageViewModel createPageViewModel( ...@@ -137,18 +144,26 @@ PageViewModel createPageViewModel(
); );
} }
class MyApp extends StatelessWidget { class MyApp extends StatefulWidget {
const MyApp({super.key}); const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final bool _skip = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<ThemeCubit>( return BlocProvider<ThemeCubit>(
create: (BuildContext context) => ThemeCubit(), create: (BuildContext context) => ThemeCubit(),
child: BlocBuilder<ThemeCubit, ThemeModeState>( child: BlocBuilder<ThemeCubit, ThemeModeState>(
builder: (BuildContext context, ThemeModeState state) { builder: (BuildContext context, ThemeModeState state) {
return MaterialApp( return ConnectivityAppWrapper(
app: MaterialApp(
/// Localization is not available for the title. /// Localization is not available for the title.
title: 'Flutter Production Boilerplate', title: 'Ğinkgo',
/// Theme stuff /// Theme stuff
theme: lightTheme, theme: lightTheme,
...@@ -160,8 +175,29 @@ class MyApp extends StatelessWidget { ...@@ -160,8 +175,29 @@ class MyApp extends StatelessWidget {
supportedLocales: context.supportedLocales, supportedLocales: context.supportedLocales,
locale: context.locale, locale: context.locale,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: const MediaQuery(data: MediaQueryData(), child: AppIntro()), home: MediaQuery(
); data: const MediaQueryData(),
child: _skip ? const SkeletonScreen() : const AppIntro(),
),
builder: (BuildContext buildContext, Widget? widget) {
return ResponsiveWrapper.builder(
ConnectivityWidgetWrapper(
message: tr('offline'),
height: 20,
child: widget!,
),
maxWidth: 480,
minWidth: 480,
// defaultScale: true,
breakpoints: <ResponsiveBreakpoint>[
// const ResponsiveBreakpoint.resize(200, name: MOBILE),
const ResponsiveBreakpoint.resize(480, name: TABLET),
const ResponsiveBreakpoint.resize(480, name: DESKTOP),
],
background: Container(color: const Color(0xFFF5F5F5)),
);
},
));
}, },
), ),
); );
......
import 'dart:typed_data';
import 'package:durt/durt.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'g1/keys_helper.dart';
import 'main.dart';
class SharedPreferencesHelper {
factory SharedPreferencesHelper() {
return _instance;
}
SharedPreferencesHelper._internal() {
SharedPreferences.getInstance().then((SharedPreferences value) {
_prefs = value;
});
}
static final SharedPreferencesHelper _instance =
SharedPreferencesHelper._internal();
late SharedPreferences _prefs;
static const String _seedKey = 'seed';
static const String _pubKey = 'pub';
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
// I'll only use shared prefs for the duniter seed
Future<void> _saveString(String key, String value) async {
await _prefs.setString(key, value);
}
Future<CesiumWallet> getWallet() async {
String? s = _getString(_seedKey);
if (s == null) {
final Uint8List uS = generateUintSeed();
s = seedToString(uS);
await _saveString(_seedKey, s);
final CesiumWallet wallet = CesiumWallet.fromSeed(uS);
logger('Generated public key: ${wallet.pubkey}');
await _saveString(_pubKey, wallet.pubkey);
return wallet;
} else {
return CesiumWallet.fromSeed(seedFromString(s));
}
}
String getPubKey() {
// At this point should exists
final String? pubkey = _prefs.getString(_pubKey);
logger('Public key $pubkey!');
return pubkey!;
}
String? _getString(String key) {
return _prefs.getString(key);
}
}
...@@ -24,130 +24,131 @@ class FifthScreen extends StatelessWidget { ...@@ -24,130 +24,131 @@ class FifthScreen extends StatelessWidget {
LinkCard( LinkCard(
title: 'code_card_title', title: 'code_card_title',
icon: Icons.code_rounded, icon: Icons.code_rounded,
url: Uri.parse( url: Uri.parse('https://git.duniter.org/public')),
'https://github.com/anfeichtinger/flutter_production_boilerplate')), if (false) const TextDivider(text: 'author_divider_title'),
const TextDivider(text: 'author_divider_title'), if (false)
GridView.count( GridView.count(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2, crossAxisCount: 2,
childAspectRatio: 2 / 1.15, childAspectRatio: 2 / 1.15,
crossAxisSpacing: 8, crossAxisSpacing: 8,
mainAxisSpacing: 8, mainAxisSpacing: 8,
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
children: <GridItem>[ children: <GridItem>[
GridItem( GridItem(
title: 'instagram_card_title', title: 'instagram_card_title',
icon: Ionicons.logo_instagram, icon: Ionicons.logo_instagram,
url: Uri.parse('https://www.instagram.com/anfeichtinger'), url: Uri.parse('https://www.instagram.com/anfeichtinger'),
), ),
GridItem( GridItem(
title: 'twitter_card_title', title: 'twitter_card_title',
icon: Ionicons.logo_twitter, icon: Ionicons.logo_twitter,
url: Uri.parse('https://twitter.com/_pharrax'), url: Uri.parse('https://twitter.com/_pharrax'),
), ),
GridItem( GridItem(
title: 'donate_card_title', title: 'donate_card_title',
icon: Ionicons.heart_outline, icon: Ionicons.heart_outline,
url: Uri.parse( url: Uri.parse(
'https://www.paypal.com/donate?hosted_button_id=EE3W7PS6AHEP8&source=url'), 'https://www.paypal.com/donate?hosted_button_id=EE3W7PS6AHEP8&source=url'),
), ),
GridItem( GridItem(
title: 'website_card_title', title: 'website_card_title',
icon: Ionicons.desktop_outline, icon: Ionicons.desktop_outline,
url: Uri.parse('https://feichtinger.dev'), url: Uri.parse('https://feichtinger.dev'),
), ),
], ],
), ),
const TextDivider(text: 'packages_divider_title'), if (false) const TextDivider(text: 'packages_divider_title'),
GridView.count( if (false)
physics: const NeverScrollableScrollPhysics(), GridView.count(
crossAxisCount: 2, physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 2 / 1.15, crossAxisCount: 2,
crossAxisSpacing: 8, childAspectRatio: 2 / 1.15,
mainAxisSpacing: 8, crossAxisSpacing: 8,
shrinkWrap: true, mainAxisSpacing: 8,
padding: EdgeInsets.zero, shrinkWrap: true,
children: <GridItem>[ padding: EdgeInsets.zero,
GridItem( children: <GridItem>[
title: 'flutter_bloc', GridItem(
icon: Ionicons.apps_outline, title: 'flutter_bloc',
url: Uri.parse( icon: Ionicons.apps_outline,
'https://pub.dev/packages/flutter_bloc/versions/8.0.1'), url: Uri.parse(
version: '8.1.1', 'https://pub.dev/packages/flutter_bloc/versions/8.0.1'),
), version: '8.1.1',
GridItem( ),
title: 'bloc', GridItem(
icon: Ionicons.grid_outline, title: 'bloc',
url: icon: Ionicons.grid_outline,
Uri.parse('https://pub.dev/packages/bloc/versions/8.1.0'), url: Uri.parse(
version: '8.1.0', 'https://pub.dev/packages/bloc/versions/8.1.0'),
), version: '8.1.0',
GridItem( ),
title: 'hydrated_bloc', GridItem(
icon: Ionicons.folder_open_outline, title: 'hydrated_bloc',
url: Uri.parse( icon: Ionicons.folder_open_outline,
'https://pub.dev/packages/hydrated_bloc/versions/8.1.0'), url: Uri.parse(
version: '9.0.0', 'https://pub.dev/packages/hydrated_bloc/versions/8.1.0'),
), version: '9.0.0',
GridItem( ),
title: 'equatable', GridItem(
icon: Ionicons.git_compare_outline, title: 'equatable',
url: Uri.parse( icon: Ionicons.git_compare_outline,
'https://pub.dev/packages/equatable/versions/2.0.3'), url: Uri.parse(
version: '2.0.5', 'https://pub.dev/packages/equatable/versions/2.0.3'),
), version: '2.0.5',
GridItem( ),
title: 'lints', GridItem(
icon: Ionicons.options_outline, title: 'lints',
url: Uri.parse( icon: Ionicons.options_outline,
'https://pub.dev/packages/flutter_lints/versions/2.0.1'), url: Uri.parse(
version: '2.0.1', 'https://pub.dev/packages/flutter_lints/versions/2.0.1'),
), version: '2.0.1',
GridItem( ),
title: 'path_provider', GridItem(
icon: Ionicons.extension_puzzle_outline, title: 'path_provider',
url: Uri.parse( icon: Ionicons.extension_puzzle_outline,
'https://pub.dev/packages/path_provider/versions/2.0.11'), url: Uri.parse(
version: '2.0.11', 'https://pub.dev/packages/path_provider/versions/2.0.11'),
), version: '2.0.11',
GridItem( ),
title: 'flutter_displaymode', GridItem(
icon: Ionicons.speedometer_outline, title: 'flutter_displaymode',
url: Uri.parse( icon: Ionicons.speedometer_outline,
'https://pub.dev/packages/flutter_displaymode/versions/0.4.0'), url: Uri.parse(
version: '0.5.0', 'https://pub.dev/packages/flutter_displaymode/versions/0.4.0'),
), version: '0.5.0',
GridItem( ),
title: 'easy_localization', GridItem(
icon: Ionicons.language_outline, title: 'easy_localization',
url: Uri.parse( icon: Ionicons.language_outline,
'https://pub.dev/packages/easy_localization/versions/3.0.1'), url: Uri.parse(
version: '3.0.1', 'https://pub.dev/packages/easy_localization/versions/3.0.1'),
), version: '3.0.1',
GridItem( ),
title: 'hive', GridItem(
icon: Ionicons.leaf_outline, title: 'hive',
url: icon: Ionicons.leaf_outline,
Uri.parse('https://pub.dev/packages/hive/versions/2.2.3'), url: Uri.parse(
version: '2.2.3', 'https://pub.dev/packages/hive/versions/2.2.3'),
), version: '2.2.3',
GridItem( ),
title: 'url_launcher', GridItem(
icon: Ionicons.share_outline, title: 'url_launcher',
url: Uri.parse( icon: Ionicons.share_outline,
'https://pub.dev/packages/url_launcher/versions/6.1.5'), url: Uri.parse(
version: '6.1.7', 'https://pub.dev/packages/url_launcher/versions/6.1.5'),
), version: '6.1.7',
GridItem( ),
title: 'ionicons', GridItem(
icon: Ionicons.logo_ionic, title: 'ionicons',
url: Uri.parse( icon: Ionicons.logo_ionic,
'https://pub.dev/packages/ionicons/versions/0.2.1'), url: Uri.parse(
version: '0.2.2', 'https://pub.dev/packages/ionicons/versions/0.2.1'),
), version: '0.2.2',
], ),
), ],
),
const SizedBox(height: 36), const SizedBox(height: 36),
]), ]),
); );
......
import 'package:another_flushbar/flushbar.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../widgets/first_screen/credit_card.dart'; import '../widgets/first_screen/credit_card.dart';
...@@ -5,11 +7,35 @@ import '../widgets/first_screen/pay_contact_search_bar.dart'; ...@@ -5,11 +7,35 @@ import '../widgets/first_screen/pay_contact_search_bar.dart';
import '../widgets/header.dart'; import '../widgets/header.dart';
import 'pay_form.dart'; import 'pay_form.dart';
class FirstScreen extends StatelessWidget { class FirstScreen extends StatefulWidget {
const FirstScreen({super.key}); const FirstScreen({super.key});
@override
State<FirstScreen> createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
bool _showFlushbar = true;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
// ignore: always_specify_types
if (_showFlushbar)
Flushbar<void>(
message: tr('demo-title'),
title: tr('demo-desc'),
duration: const Duration(seconds: 4),
flushbarPosition: FlushbarPosition.TOP,
onStatusChanged: (FlushbarStatus? status) {
if (status == FlushbarStatus.DISMISSED) {
setState(() {
_showFlushbar = false;
});
}
},
).show(context);
});
return Material( return Material(
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
child: ListView( child: ListView(
...@@ -17,7 +43,7 @@ class FirstScreen extends StatelessWidget { ...@@ -17,7 +43,7 @@ class FirstScreen extends StatelessWidget {
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
children: <Widget>[ children: <Widget>[
const Header(text: 'credit_card_title'), const Header(text: 'credit_card_title'),
const CreditCard(), CreditCard(),
const SizedBox(height: 8), const SizedBox(height: 8),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 24),
......
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