diff --git a/.gitignore b/.gitignore index 603d6ca8b1ef10bd23284f39239d10f8d8a93466..5615bc8b917cd3c8bff30920bfce6860c09f7769 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,5 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release -web build.sh -linux/ \ No newline at end of file +linux/ diff --git a/assets/translations/ca.json b/assets/translations/ca.json index f5184ee4a1d152ea3ac8864c4b258037ae761cb5..a475ce92482962c5c9efbd86a8a46978852943a4 100644 --- a/assets/translations/ca.json +++ b/assets/translations/ca.json @@ -56,7 +56,6 @@ "data_load_error": "Error en carregar les dades", "add_contact": "Afegeix el contacte", "contact_added": "Contacte afegit", - "no_transactions": "Encara no tens cap transacció", "qr-scanner-title": "Escaneja el QR d'algú", "copy_contact_key": "Copia", "nothing_found": "No s'ha trobat res", diff --git a/assets/translations/en.json b/assets/translations/en.json index 98e2c41502e49c09b3743d62b1bcd14414126bc7..c44742e661f7235b2d4963a4b7bcf1a7f0f92df7 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -56,7 +56,7 @@ "data_load_error": "Error loading data", "add_contact": "Add contact", "contact_added": "Contact added", - "no_transactions": "You don't have any transaction yet", + "no_transactions": "This wallet has no balance. Start by offering your services in Äž1 markets, for example, to receive your first income.", "qr-scanner-title": "Scan the QR of someone", "copy_contact_key": "Copy", "nothing_found": "Nothing found", @@ -88,5 +88,6 @@ "please_confirm_sent_desc": "Please confirm that you wish to send {amount} Äž1 to {to}", "yes_sent": "YES, SEND IT", "receive_g1": "Receive Äž1", - "insufficient balance": "Oops, insufficient balance to pay this" + "insufficient balance": "Oops, insufficient balance to pay this", + "language_switch_title": "Select your language" } diff --git a/assets/translations/es.json b/assets/translations/es.json index 2a788d81a749d0f9aa5165794c3a49ac08bf9b5a..082d49fc6fb87debbf5d8528491737b4d3725963 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -56,7 +56,7 @@ "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", + "no_transactions": "Este monedero no tiene saldo. Empieza por ejemplo a ofrecer tus servicios en mercados Äž1 para recibir tus primeros ingresos", "qr-scanner-title": "Escanea el QR de alguien", "copy_contact_key": "Copiar", "nothing_found": "No se ha encontrado nada", @@ -87,5 +87,6 @@ "please_confirm_sent_desc": "Por favor confirma que quieres enviar {amount} Äž1 a {to}", "yes_sent": "SÃ, ENVÃALO", "receive_g1": "Recibe Junas", - "insufficient balance": "Upps, balance insuficiente para hacer este pago" + "insufficient balance": "Upps, balance insuficiente para hacer este pago", + "language_switch_title": "Selecciona tu idioma" } diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 6a0f5ceeec4a74feb4cdc2382099a03c27be52c5..165273fc7496761db7b8b52d285c2b185eca239e 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -19,7 +19,7 @@ "g1_form_pay_hint": "Entrez une description (facultatif)", "code_card_title": "Dépôt de code", "intro_1_title": "Bienvenue dans notre portefeuille Äž1!", - "intro_1_description": "Avec ce portefeuille, vous pouvez facilement et en toute sécurité stocker, envoyer et recevoir de la monnaie Äž1 (également connue sous le nom de 'Juin').", + "intro_1_description": "Avec ce portefeuille, vous pouvez facilement et en toute sécurité stocker, envoyer et recevoir de la monnaie Äž1 (également connue sous le nom de 'June').", "intro_2_title": "Une monnaie numérique libre créée par le peuple, pour le peuple", "intro_2_description": "Äž1 ne dépend d'aucun gouvernement ou entreprise et est écologique (car elle consomme peu d'énergie), transparente et équitable pour tous.", "intro_3_title": "La monnaie Äž1 fonctionne sur le réseau Duniter", @@ -63,4 +63,4 @@ "intro_some_pattern_to_export": "Motif pour importer/exporter votre portefeuille", "confirm_pattern": "Confirmer le motif", "draw_pattern": "Dessiner le motif" -} \ No newline at end of file +} diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 69c8dbd170162daa391861ea8334cf5808a9d6ff..cb6950551df449258d980761203c7a5ff1b0daaa 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -46,6 +46,7 @@ <string>en</string> <string>es</string> <string>fr</string> + <string>ca</string> </array> <key>LSApplicationQueriesSchemes</key> <array> diff --git a/lib/data/models/app_state.dart b/lib/data/models/app_state.dart index 76c8b2ca76f8da0a938a1ffa62f076b2f333e595..99d588d4bf0b26136a76a0b810f4b86e0f4909a0 100644 --- a/lib/data/models/app_state.dart +++ b/lib/data/models/app_state.dart @@ -11,6 +11,7 @@ class AppState extends Equatable implements IsJsonSerializable<AppState> { this.introViewed = false, this.warningViewed = false, this.expertMode = false, + this.locale = 'en', }); factory AppState.fromJson(Map<String, dynamic> json) => @@ -19,16 +20,19 @@ class AppState extends Equatable implements IsJsonSerializable<AppState> { final bool introViewed; final bool warningViewed; final bool expertMode; + final String locale; AppState copyWith({ bool? introViewed, bool? warningViewed, bool? expertMode, + String? locale, }) { return AppState( introViewed: introViewed ?? this.introViewed, warningViewed: warningViewed ?? this.warningViewed, - expertMode: expertMode ?? this.expertMode); + expertMode: expertMode ?? this.expertMode, + locale: locale ?? this.locale); } @override @@ -38,5 +42,6 @@ class AppState extends Equatable implements IsJsonSerializable<AppState> { Map<String, dynamic> toJson() => _$AppStateToJson(this); @override - List<Object?> get props => <Object>[introViewed, warningViewed, expertMode]; + List<Object?> get props => + <Object>[introViewed, warningViewed, expertMode, locale]; } diff --git a/lib/data/models/app_state.g.dart b/lib/data/models/app_state.g.dart index dca06c2fc3f6db2daa603fd305b2ad99e6141664..8e1120d54c2e362818cc95429e7e4ba91c038ab0 100644 --- a/lib/data/models/app_state.g.dart +++ b/lib/data/models/app_state.g.dart @@ -10,10 +10,12 @@ 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, + locale: json['locale'] as String? ?? 'en', ); Map<String, dynamic> _$AppStateToJson(AppState instance) => <String, dynamic>{ 'introViewed': instance.introViewed, 'warningViewed': instance.warningViewed, 'expertMode': instance.expertMode, + 'locale': instance.locale, }; diff --git a/lib/data/models/node_list_cubit.dart b/lib/data/models/node_list_cubit.dart index 2136aea828f9aef2af092acad789efea76e8c958..c6e213ac48ca440c2018c6e88948986a869ab133 100644 --- a/lib/data/models/node_list_cubit.dart +++ b/lib/data/models/node_list_cubit.dart @@ -6,34 +6,6 @@ import 'node_list_state.dart'; class NodeListCubit extends HydratedCubit<NodeListState> { NodeListCubit() : super(NodeListState()); - void addDuniterNode(Node node) { - if (!_find(node)) { - // Does not exists, so add it - emit(state.copyWith(duniterNodes: <Node>[...state.duniterNodes, node])); - } else { - // it exists - updateDuniterNode(node); - } - } - - bool _find(Node node) => state.duniterNodes.contains(node); - - void insertDuniterNode(Node node) { - if (!_find(node)) { - emit(state.copyWith(duniterNodes: <Node>[node, ...state.duniterNodes])); - } else { - // it exists - updateDuniterNode(node); - } - } - - void updateDuniterNode(Node updatedNode) { - final List<Node> updatedDuniterNodes = state.duniterNodes.map((Node n) { - return n.url == updatedNode.url ? updatedNode : n; - }).toList(); - emit(state.copyWith(duniterNodes: updatedDuniterNodes)); - } - void setDuniterNodes(List<Node> nodes) { emit(state.copyWith(duniterNodes: nodes)); } @@ -42,9 +14,8 @@ class NodeListCubit extends HydratedCubit<NodeListState> { emit(state.copyWith(cesiumPlusNodes: nodes)); } - void addCesiumPlusNode(Node node) { - emit(state - .copyWith(cesiumPlusNodes: <Node>[...state.cesiumPlusNodes, node])); + void setGvaNodes(List<Node> nodes) { + emit(state.copyWith(gvaNodes: nodes)); } List<Node> get duniterNodes => state.duniterNodes; diff --git a/lib/data/models/node_manager.dart b/lib/data/models/node_manager.dart index ccf9e3825fe565ca6878c31e7f584eaf38e153e3..4f2383e71a53bea35abddf6b99861c2552deb476 100644 --- a/lib/data/models/node_manager.dart +++ b/lib/data/models/node_manager.dart @@ -119,5 +119,6 @@ class NodeManagerObserver { void update(NodeManager nodeManager) { cubit.setDuniterNodes(nodeManager.duniterNodes); cubit.setCesiumPlusNodes(nodeManager.cesiumPlusNodes); + cubit.setGvaNodes(nodeManager.gvaNodes); } } diff --git a/lib/data/models/payment_cubit.dart b/lib/data/models/payment_cubit.dart index 43f048449eaa3377f308b05fcf4a26b98d5623a4..184812277b65fa0bb8dcd1747f9ca5b05d3b817a 100644 --- a/lib/data/models/payment_cubit.dart +++ b/lib/data/models/payment_cubit.dart @@ -29,7 +29,7 @@ class PaymentCubit extends HydratedCubit<PaymentState> { void selectKeyAmount(String publicKey, double amount) { final PaymentState newState = - PaymentState(publicKey: publicKey, amount: amount); + PaymentState(publicKey: publicKey, amount: amount); emit(newState); } @@ -72,7 +72,7 @@ class PaymentCubit extends HydratedCubit<PaymentState> { emit(newState); } - void setDescription(String description) { - emit(state.copyWith(comment: description)); + void setComment(String comment) { + emit(state.copyWith(comment: comment)); } } diff --git a/lib/data/models/transaction.dart b/lib/data/models/transaction.dart index 4218a9e04a22d8af5ed8b0bad8a018f0e911469d..9fec0435f076455b287bc17bc4b4bb6d0647facc 100644 --- a/lib/data/models/transaction.dart +++ b/lib/data/models/transaction.dart @@ -30,7 +30,7 @@ class Transaction extends Equatable { @JsonKey(fromJson: uIntFromList, toJson: uIntToList) final Uint8List? toAvatar; final String? toNick; - final int amount; + final double amount; @JsonKey(fromJson: uIntFromList, toJson: uIntToList) final Uint8List? fromAvatar; final String? fromNick; @@ -65,7 +65,7 @@ class TransactionsAndBalanceState extends Equatable { factory TransactionsAndBalanceState.fromJson(Map<String, dynamic> json) => _$TransactionsAndBalanceStateFromJson(json); final List<Transaction> transactions; - final int balance; + final double balance; final DateTime lastChecked; Map<String, dynamic> toJson() => _$TransactionsAndBalanceStateToJson(this); diff --git a/lib/data/models/transaction.g.dart b/lib/data/models/transaction.g.dart index 992e707be310c1fcce75b6cc6367ac8324f0a4e5..62df6223e9f799e27394b8285adccc5afb16aca5 100644 --- a/lib/data/models/transaction.g.dart +++ b/lib/data/models/transaction.g.dart @@ -11,7 +11,7 @@ abstract class _$TransactionCWProxy { Transaction to(String to); - Transaction amount(int amount); + Transaction amount(double amount); Transaction comment(String comment); @@ -34,7 +34,7 @@ abstract class _$TransactionCWProxy { Transaction call({ String? from, String? to, - int? amount, + double? amount, String? comment, DateTime? time, Uint8List? toAvatar, @@ -57,7 +57,7 @@ class _$TransactionCWProxyImpl implements _$TransactionCWProxy { Transaction to(String to) => this(to: to); @override - Transaction amount(int amount) => this(amount: amount); + Transaction amount(double amount) => this(amount: amount); @override Transaction comment(String comment) => this(comment: comment); @@ -108,7 +108,7 @@ class _$TransactionCWProxyImpl implements _$TransactionCWProxy { amount: amount == const $CopyWithPlaceholder() || amount == null ? _value.amount // ignore: cast_nullable_to_non_nullable - : amount as int, + : amount as double, comment: comment == const $CopyWithPlaceholder() || comment == null ? _value.comment // ignore: cast_nullable_to_non_nullable @@ -146,7 +146,7 @@ extension $TransactionCopyWith on Transaction { abstract class _$TransactionsAndBalanceStateCWProxy { TransactionsAndBalanceState transactions(List<Transaction> transactions); - TransactionsAndBalanceState balance(int balance); + TransactionsAndBalanceState balance(double balance); TransactionsAndBalanceState lastChecked(DateTime lastChecked); @@ -158,7 +158,7 @@ abstract class _$TransactionsAndBalanceStateCWProxy { /// ```` TransactionsAndBalanceState call({ List<Transaction>? transactions, - int? balance, + double? balance, DateTime? lastChecked, }); } @@ -175,7 +175,7 @@ class _$TransactionsAndBalanceStateCWProxyImpl this(transactions: transactions); @override - TransactionsAndBalanceState balance(int balance) => this(balance: balance); + TransactionsAndBalanceState balance(double balance) => this(balance: balance); @override TransactionsAndBalanceState lastChecked(DateTime lastChecked) => @@ -203,7 +203,7 @@ class _$TransactionsAndBalanceStateCWProxyImpl balance: balance == const $CopyWithPlaceholder() || balance == null ? _value.balance // ignore: cast_nullable_to_non_nullable - : balance as int, + : balance as double, lastChecked: lastChecked == const $CopyWithPlaceholder() || lastChecked == null ? _value.lastChecked @@ -227,7 +227,7 @@ extension $TransactionsAndBalanceStateCopyWith on TransactionsAndBalanceState { Transaction _$TransactionFromJson(Map<String, dynamic> json) => Transaction( from: json['from'] as String, to: json['to'] as String, - amount: json['amount'] as int, + amount: (json['amount'] as num).toDouble(), comment: json['comment'] as String, time: DateTime.parse(json['time'] as String), toAvatar: uIntFromList(json['toAvatar'] as List<int>), @@ -255,7 +255,7 @@ TransactionsAndBalanceState _$TransactionsAndBalanceStateFromJson( transactions: (json['transactions'] as List<dynamic>) .map((e) => Transaction.fromJson(e as Map<String, dynamic>)) .toList(), - balance: json['balance'] as int, + balance: (json['balance'] as num).toDouble(), lastChecked: DateTime.parse(json['lastChecked'] as String), ); diff --git a/lib/data/models/transaction_cubit.dart b/lib/data/models/transaction_cubit.dart index 0f2ea8560dfbf087eb300b55bf396b38e7e6367c..6e6282451c463ba7bc2b4ec5be5895e3f40d452d 100644 --- a/lib/data/models/transaction_cubit.dart +++ b/lib/data/models/transaction_cubit.dart @@ -19,28 +19,19 @@ class TransactionsCubit extends HydratedCubit<TransactionsAndBalanceState> { final TransactionsAndBalanceState currentState = state; final List<Transaction> newTransactions = List<Transaction>.of(currentState.transactions)..add(transaction); - final int newBalance = currentState.balance + transaction.amount; + final double newBalance = currentState.balance + transaction.amount; emit(currentState.copyWith( transactions: newTransactions, balance: newBalance)); } - void updateTransactions(List<Transaction> newTransactions, int newBalance) { + void updateTransactions( + List<Transaction> newTransactions, double newBalance) { emit(state.copyWith(transactions: newTransactions, balance: newBalance)); } Future<void> fetchTransactions(NodeListCubit cubit) async { - // Future<TransactionsAndBalance> _loadTransactions(NodeListCubit cubit) async { - // carga de datos asÃncrona - // ... - // disabled, as we have to change the nodes - // https://g1.asycn.io/gva - // https://duniter.pini.fr/gva - /* Gva(node: 'https://g1.asycn.io/gva') - .balance(SharedPreferencesHelper().getPubKey()) - .then((double currentBal) => setState(() { - _balanceAmount = currentBal; - })); */ logger('Loading transactions'); + logger('GVA balance: ${gvaBalance()}'); final String txData = txDebugging ? await getTxHistory('6DrGg8cftpkgffv4Y4Lse9HSjgc8coEQor3yvMPHAnVH') : await getTxHistory(SharedPreferencesHelper().getPubKey()); @@ -61,7 +52,7 @@ class TransactionsCubit extends HydratedCubit<TransactionsAndBalanceState> { List<Transaction> get transactions => state.transactions; - int get balance => state.balance; + double get balance => state.balance; DateTime get lastChecked => state.lastChecked; } diff --git a/lib/g1/api.dart b/lib/g1/api.dart index cfe6dcf5382db0039a388ae6c73d94f7ae529e5b..8773f85a040bc55615b2d290773f5d27af718404 100644 --- a/lib/g1/api.dart +++ b/lib/g1/api.dart @@ -405,17 +405,11 @@ Future<http.Response> _requestWithRetry( Future<String> pay( {required String to, required double amount, String? comment}) async { - final List<Node> nodes = nodesWorkingList(NodeType.gva); - if (nodes.isNotEmpty) { - // reorder list to use others - nodes.shuffle(); - + final String output = getGvaNode(); + if (Uri.tryParse(output) != null) { + final String node = output; try { - // Reference of working proxy 'https://g1demo.comunes.net/proxy/g1v1.p2p.legal/gva/'; - final String node = - 'https://g1demo.comunes.net/proxy/${nodes.first.url.replaceFirst('https://', '').replaceFirst('http://', '')}/'; final Gva gva = Gva(node: node); - logger('Trying $node to get balance'); final CesiumWallet wallet = await SharedPreferencesHelper().getWallet(); logger('Current balance ${await gva.balance(wallet.pubkey)}'); @@ -430,11 +424,45 @@ Future<String> pay( logger('GVA replied with "$response"'); return response; } catch (e, stacktrace) { - // move logger outside main logger(e); logger(stacktrace); return "Oops! the payment failed. Something didn't work as expected"; } } - return 'Sorry: I cannot find a working node to send the transaction'; + return output; +} + +String getGvaNode() { + final List<Node> nodes = nodesWorkingList(NodeType.gva); + if (nodes.isNotEmpty) { +// reorder list to use others + nodes.shuffle(); +// Reference of working proxy 'https://g1demo.comunes.net/proxy/g1v1.p2p.legal/gva/'; + final String node = + 'https://g1demo.comunes.net/proxy/${nodes.first.url.replaceFirst('https://', '').replaceFirst('http://', '')}/'; + return node; + } else { + return 'Sorry: I cannot find a working node to send the transaction'; + } +} + +Future<double> gvaBalance() async { + final String output = getGvaNode(); + if (Uri.tryParse(output) != null) { + final String node = output; + try { + final Gva gva = Gva(node: node); + logger('Trying $node to get balance'); + final CesiumWallet wallet = await SharedPreferencesHelper().getWallet(); + final double balance = await gva.balance(wallet.pubkey); + logger('Current balance $balance'); + return balance; + } catch (e, stacktrace) { + // move logger outside main + logger(e); + logger(stacktrace); + throw Exception('Oops! failed to obtain balance'); + } + } + throw Exception('Sorry: I cannot find a working node to get your balance'); } diff --git a/lib/g1/transaction_parser.dart b/lib/g1/transaction_parser.dart index dd021a8cc404eacc70b53b83e1a6a9b5871372c0..cfcbdc43314e8ea57fb5b27d73245cd031bf3290 100644 --- a/lib/g1/transaction_parser.dart +++ b/lib/g1/transaction_parser.dart @@ -10,14 +10,14 @@ TransactionsAndBalanceState transactionParser(String txData) { final String pubKey = parsedTxData['pubkey'] as String; final List<dynamic> listReceived = (parsedTxData['history'] as Map<String, dynamic>)['received'] as List<dynamic>; - int balance = 0; + double balance = 0; final List<Transaction> tx = <Transaction>[]; for (final dynamic receivedRaw in listReceived) { final Map<String, dynamic> received = receivedRaw as Map<String, dynamic>; final int timestamp = received['blockstampTime'] as int; final String comment = received['comment'] as String; final List<dynamic> outputs = received['outputs'] as List<dynamic>; - final int amount = int.parse((outputs[0] as String).split(':')[0]); + final double amount = double.parse((outputs[0] as String).split(':')[0]); final String? address1 = exp.firstMatch(outputs[0] as String)!.group(1); final String? address2 = exp.firstMatch(outputs[1] as String)!.group(1); if (pubKey == address1) { diff --git a/lib/main.dart b/lib/main.dart index 85cc6fb8d14dc01dd68b21612baad27f3e0df54b..d24385f3156726a7a00e3e455b50f704265e5600 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -78,6 +78,7 @@ void main() async { Locale('en'), Locale('es'), Locale('fr'), + Locale('ca'), ], fallbackLocale: const Locale('en'), useFallbackTranslations: true, @@ -144,7 +145,8 @@ class _AppIntro extends State<AppIntro> { createPageViewModel( 'intro_${i}_title', 'intro_${i}_description', - 'assets/img/undraw_intro_$i.png'), + 'assets/img/undraw_intro_$i.png', + context), ], onDone: () => _onIntroEnd(buildContext), showSkipButton: true, @@ -169,15 +171,26 @@ class _AppIntro extends State<AppIntro> { } PageViewModel createPageViewModel( - String title, String body, String imageAsset) { + String title, String body, String imageAsset, BuildContext context) { + final ColorScheme colorScheme = Theme.of(context).colorScheme; + final TextStyle titleStyle = TextStyle( + color: colorScheme.primary, + fontWeight: FontWeight.bold, + fontSize: 24.0, + ); + final TextStyle bodyStyle = TextStyle( + color: colorScheme.onSurface, + fontSize: 18.0, + ); + return PageViewModel( title: tr(title), body: tr(body), image: Image.asset(imageAsset), - decoration: const PageDecoration( - pageColor: Colors.white, - bodyTextStyle: TextStyle(fontSize: 18), - titleTextStyle: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + decoration: PageDecoration( + titleTextStyle: titleStyle, + bodyTextStyle: bodyStyle, + pageColor: colorScheme.background, ), ); } diff --git a/lib/shared_prefs.dart b/lib/shared_prefs.dart index 20401c207b3f130c2f9d2eb792015f415f5763ff..780f5612f68f979f96e2c459f91b88af70679202 100644 --- a/lib/shared_prefs.dart +++ b/lib/shared_prefs.dart @@ -82,7 +82,6 @@ class SharedPreferencesHelper { String getPubKey() { // At this point should exists final String? pubKey = _prefs.getString(_pubKey); - logger('Public key: $pubKey'); return pubKey!; } diff --git a/lib/ui/screens/fifth_screen.dart b/lib/ui/screens/fifth_screen.dart index 98e197949cf1adaf2099fd6ce7ba16451535b3db..400169203659036bb6be80e5e72db67bb66a404e 100644 --- a/lib/ui/screens/fifth_screen.dart +++ b/lib/ui/screens/fifth_screen.dart @@ -28,6 +28,38 @@ class FifthScreen extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16), physics: const BouncingScrollPhysics(), children: <Widget>[ + const SizedBox(height: 10), + DropdownButtonFormField<Locale>( + value: context.locale, + decoration: InputDecoration( + labelText: tr('language_switch_title'), + icon: const Icon(Icons.language), + border: const OutlineInputBorder(), + ), + onChanged: (Locale? newLocale) { + context.setLocale(newLocale!); + }, + items: const <DropdownMenuItem<Locale>>[ + DropdownMenuItem<Locale>( + value: Locale('ca'), + child: Text('Català '), + ), + DropdownMenuItem<Locale>( + value: Locale('en'), + child: Text('English'), + ), + DropdownMenuItem<Locale>( + value: Locale('es'), + child: Text('Español'), + ), + DropdownMenuItem<Locale>( + value: Locale('fr'), + child: Text('Français'), + ), + + // Add more DropdownMenuItem for more languages + ], + ), const TextDivider(text: 'key_tools_title'), GridView.count( physics: const NeverScrollableScrollPhysics(), diff --git a/lib/ui/screens/first_screen.dart b/lib/ui/screens/first_screen.dart index ddd1b72044fc25fae0d072120a65aadb90ce2948..74ba6f2dff15db5d49cd028eed340e6b713e7bad 100644 --- a/lib/ui/screens/first_screen.dart +++ b/lib/ui/screens/first_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../data/models/app_cubit.dart'; +import '../../data/models/app_state.dart'; import '../../data/models/payment_cubit.dart'; import '../../data/models/payment_state.dart'; import '../widgets/bottom_widget.dart'; @@ -20,94 +21,66 @@ class FirstScreen extends StatefulWidget { class _FirstScreenState extends State<FirstScreen> { @override - Widget build(BuildContext context) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (!context.read<AppCubit>().isWarningViewed) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(tr('demo_desc')), - action: SnackBarAction( - label: 'OK', - onPressed: () { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - context.read<AppCubit>().warningViewed(); - }, - ), - ), - ); - } - }); - return BlocBuilder<PaymentCubit, PaymentState>( - builder: (BuildContext context, PaymentState state) => - Stack(children: <Widget>[ - Scaffold( - appBar: AppBar(title: Text(tr('credit_card_title'))), - drawer: const CardDrawer(), - body: ListView( - padding: const EdgeInsets.symmetric(horizontal: 16), - //physics: const AlwaysScrollableScrollPhysics(), - //controller: _controller, - // shrinkWrap: true, - children: <Widget>[ - CreditCard(), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Divider( - color: Theme.of(context) - .colorScheme - .onBackground - .withOpacity(.4), + Widget build(BuildContext context) => + BlocBuilder<AppCubit, AppState>( + builder: (BuildContext context, AppState state) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (!state.warningViewed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(tr('demo_desc')), + action: SnackBarAction( + label: 'OK', + onPressed: () { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + context.read<AppCubit>().warningViewed(); + }, + ), + ), + ); + } + }); + return BlocBuilder<PaymentCubit, PaymentState>( + builder: (BuildContext context, PaymentState state) => + Stack(children: <Widget>[ + Scaffold( + appBar: AppBar(title: Text(tr('credit_card_title'))), + drawer: const CardDrawer(), + body: ListView( + padding: const EdgeInsets.symmetric( + horizontal: 16), + //physics: const AlwaysScrollableScrollPhysics(), + //controller: _controller, + // shrinkWrap: true, + children: <Widget>[ + CreditCard(), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24), + child: Divider( + color: Theme + .of(context) + .colorScheme + .onBackground + .withOpacity(.4), + ), + ), + const SizedBox(height: 10), + const PayContactSearchButton(), + const SizedBox(height: 10), + const PayForm(), + const BottomWidget() + ])), + Visibility( + visible: state.status == PaymentStatus.sending, + child: Container( + color: Colors.black.withOpacity(0.5), + child: const Center( + child: CircularProgressIndicator(), ), ), - const SizedBox(height: 10), - const PayContactSearchButton(), - const SizedBox(height: 10), - const PayForm(), - const BottomWidget() - ])), - Visibility( - visible: state.status == PaymentStatus.sending, - child: Container( - color: Colors.black.withOpacity(0.5), - child: const Center( - child: CircularProgressIndicator(), - ), - ), - ) - ])); - } + ) + ])); + }); } -/* -Scaffold( - appBar: AppBar( - leading: IconButton( - icon: Icon(Icons.menu), - onPressed: () => Scaffold.of(context).openDrawer(), - ), - title: Text('My App'), - ), - drawerEnableOpenDragGesture: true, - drawer: CustomDrawer(), - body: // ... -); - -class Screen1 extends StatelessWidget { - const Screen1({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Screen 1')), - drawer: CustomDrawer(), - body: Center( - child: ElevatedButton( - child: Text('Open Drawer'), - onPressed: () { - Scaffold.of(context).openDrawer(); - }, - ), - ), - ); - } -} */ diff --git a/lib/ui/screens/fourth_screen.dart b/lib/ui/screens/fourth_screen.dart index 680ca6aefc5064690f439420367a0a08c84ebcf7..7a67c4b058b03b03ac5609b4f0fd4fb77d319c68 100644 --- a/lib/ui/screens/fourth_screen.dart +++ b/lib/ui/screens/fourth_screen.dart @@ -1,7 +1,5 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import '../widgets/card_drawer.dart'; import '../widgets/fourth_screen/transaction_page.dart'; class FourthScreen extends StatelessWidget { @@ -9,9 +7,6 @@ class FourthScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(tr('transactions'))), - drawer: const CardDrawer(), - body: const TransactionsAndBalanceWidget()); + return const TransactionsAndBalanceWidget(); } } diff --git a/lib/ui/screens/pay_form.dart b/lib/ui/screens/pay_form.dart index 3a629950770c24dc1f48e64ac8e7557ef1977a0c..af51e2f6afc613cda65c8cb3cd22f781592a6d71 100644 --- a/lib/ui/screens/pay_form.dart +++ b/lib/ui/screens/pay_form.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../data/models/payment_cubit.dart'; @@ -36,9 +37,12 @@ class _PayFormState extends State<PayForm> { const SizedBox(height: 10.0), TextField( controller: _commentController, + inputFormatters: <TextInputFormatter>[ + Iso88591TextInputFormatter() + ], onChanged: (String? value) { if (value != null) { - context.read<PaymentCubit>().setDescription(value); + context.read<PaymentCubit>().setComment(value); } }, decoration: InputDecoration( @@ -139,3 +143,18 @@ class _PayFormState extends State<PayForm> { ); } } + +class Iso88591TextInputFormatter extends TextInputFormatter { + static final RegExp _iso88591RegExp = RegExp('^[\u0000-\u00FF]*\$'); + static final RegExp _englishRegExp = RegExp('^[\u0000-\u007F]*\$'); + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + if (_englishRegExp.hasMatch(newValue.text)) { + return newValue; + } else { + return oldValue; + } + } +} diff --git a/lib/ui/ui_helpers.dart b/lib/ui/ui_helpers.dart index da2c3924b73e8ff663f39bf1cf31a55c7bd02bfb..b3f5a916a30b09e230dc68e1b26713f8daa3d8f4 100644 --- a/lib/ui/ui_helpers.dart +++ b/lib/ui/ui_helpers.dart @@ -70,8 +70,14 @@ Widget humanizePubKeyAsWidget(String pubKey) => Text( ), ); -Color tileColor(int index, [bool inverse = false]) => - (inverse ? index.isOdd : index.isEven) ? Colors.grey[200]! : Colors.white; +Color tileColor(int index, BuildContext context, [bool inverse = false]) { + final ColorScheme colorScheme = Theme.of(context).colorScheme; + final Color selectedColor = colorScheme.primary.withOpacity(0.1); + final Color unselectedColor = colorScheme.surface; + return (inverse ? index.isOdd : index.isEven) + ? selectedColor + : unselectedColor; +} String? humanizeTime(DateTime time, String locale) => timeago.format(time, locale: locale, clock: DateTime.now()); @@ -85,3 +91,7 @@ bool bigScreen(BuildContext context) => bool smallScreen(BuildContext context) => MediaQuery.of(context).size.width <= smallScreenWidth; + +String formatAmount(double amount) => amount.toStringAsFixed(2); + +String formatKAmount(double amount) => formatAmount(amount / 100); diff --git a/lib/ui/widgets/fifth_screen/import_dialog.dart b/lib/ui/widgets/fifth_screen/import_dialog.dart index b113eeefe6b6a1ec15b6e6401f8ab32ba5020da8..9d4bdc89a0110dae5bda6cc7c959204f4cb16ece 100644 --- a/lib/ui/widgets/fifth_screen/import_dialog.dart +++ b/lib/ui/widgets/fifth_screen/import_dialog.dart @@ -75,7 +75,7 @@ class _ImportDialogState extends State<ImportDialog> { context.replaceSnackbar( content: Text( tr('wallet_imported'), - style: const TextStyle(color: Colors.blue), + style: const TextStyle(color: Colors.white), ), ); } diff --git a/lib/ui/widgets/first_screen/credit_card.dart b/lib/ui/widgets/first_screen/credit_card.dart index 07b82d03cc976be075ab47f120b6f4f9488a7a3e..8e3be06b27f7f47683833c75a0b1f4b55ced042e 100644 --- a/lib/ui/widgets/first_screen/credit_card.dart +++ b/lib/ui/widgets/first_screen/credit_card.dart @@ -158,17 +158,25 @@ class CreditCard extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(16.0), child: GestureDetector( - onTap: () => copyPublicKeyToClipboard(context), - child: Column(children: <Widget>[ - Text(tr('show_qr_to_client')), - QrImage( - data: publicKey, - size: MediaQuery.of(context).size.width * 0.8, - gapless: false, - foregroundColor: Colors.brown, - ), - ]), - )))); + onTap: () => copyPublicKeyToClipboard(context), + child: Container( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.grey[900] + : Colors.grey[100], + padding: const EdgeInsets.all(16.0), + child: Column(children: <Widget>[ + Text(tr('show_qr_to_client')), + QrImage( + data: publicKey, + size: MediaQuery.of(context).size.width * 0.8, + gapless: false, + foregroundColor: Theme.of(context).brightness == + Brightness.dark + ? Colors.white + : Colors.black, + ), + ]), + ))))); }, ); } diff --git a/lib/ui/widgets/first_screen/pay_contact_search_page.dart b/lib/ui/widgets/first_screen/pay_contact_search_page.dart index c55905d9f2ed4f1718cf223a8ed75f54f09775ab..31adea51209efc4ee54ca9dbee3a30b25a0efb09 100644 --- a/lib/ui/widgets/first_screen/pay_contact_search_page.dart +++ b/lib/ui/widgets/first_screen/pay_contact_search_page.dart @@ -200,7 +200,7 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { return ListTile( title: Text(title), subtitle: subtitle, - tileColor: tileColor(index), + tileColor: tileColor(index, context), onTap: () { context.read<PaymentCubit>().selectUser(pubKey, contact.nick ?? contact.name, hasAvatar ? contact.avatar : null); @@ -208,8 +208,8 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { }, leading: avatar( contact.avatar, - bgColor: tileColor(index), - color: tileColor(index, true), + bgColor: tileColor(index, context), + color: tileColor(index, context, true), ), trailing: BlocBuilder<ContactsCubit, ContactsState>( builder: (BuildContext context, ContactsState state) { diff --git a/lib/ui/widgets/fourth_screen/transaction_page.dart b/lib/ui/widgets/fourth_screen/transaction_page.dart index 713757361a39a9569edeece8c1651791f759998d..6a596a74850602999da20f7caa32b1d78f147dae 100644 --- a/lib/ui/widgets/fourth_screen/transaction_page.dart +++ b/lib/ui/widgets/fourth_screen/transaction_page.dart @@ -11,6 +11,7 @@ import '../../../data/models/transaction.dart'; import '../../../data/models/transaction_cubit.dart'; import '../../../shared_prefs.dart'; import '../../ui_helpers.dart'; +import '../card_drawer.dart'; import '../header.dart'; import '../loading_box.dart'; import '../third_screen/contacts_page.dart'; @@ -28,6 +29,9 @@ class _TransactionsAndBalanceWidgetState extends State<TransactionsAndBalanceWidget> with SingleTickerProviderStateMixin { final ScrollController _transScrollController = ScrollController(); + final DraggableScrollableController _draggableScrollableController = + DraggableScrollableController(); + late NodeListCubit nodeListCubit; late TransactionsCubit transCubit; bool isLoading = false; @@ -52,16 +56,20 @@ class _TransactionsAndBalanceWidgetState if (_transScrollController.position.pixels == _transScrollController.position.maxScrollExtent || _transScrollController.offset == 0) { - setState(() { - isLoading = true; - }); - await transCubit.fetchTransactions(nodeListCubit); - setState(() { - isLoading = false; - }); + await _refreshTransactions(); } } + Future<void> _refreshTransactions() async { + setState(() { + isLoading = true; + }); + await transCubit.fetchTransactions(nodeListCubit); + setState(() { + isLoading = false; + }); + } + @override Widget build(BuildContext context) { final String myPubKey = !txDebugging @@ -73,148 +81,181 @@ class _TransactionsAndBalanceWidgetState // TODO(vjrj): Only fetch last transactions and used persisted ones final ContactsCubit contactsCubit = context.read<ContactsCubit>(); final List<Transaction> transactions = transBalanceState.transactions; - final int balance = transBalanceState.balance; + final double balance = transBalanceState.balance; if (!isLoading) { - return Stack(children: <Widget>[ - Column(crossAxisAlignment: CrossAxisAlignment.start, children: < - Widget>[ - /* Container( + return Scaffold( + appBar: AppBar( + title: Text(tr('transactions')), + actions: <Widget>[ + IconButton( + icon: const Icon(Icons.refresh), + onPressed: () { + _refreshTransactions(); + }, + ), + if (!kReleaseMode) + IconButton( + onPressed: () { + if (_draggableScrollableController.size != null) { + _draggableScrollableController.animateTo( + 100, + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + } + }, + icon: const Icon(Icons.savings), + ), + ], + ), + drawer: const CardDrawer(), + body: Stack(children: <Widget>[ + Column(crossAxisAlignment: CrossAxisAlignment.start, children: < + Widget>[ + /* Container( color: Theme.of(context).colorScheme.surfaceVariant, height: 90, width: double.infinity, child: const Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Header(text: 'transactions'))), */ - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 50), - child: transactions.isEmpty - ? Column(children: const <Widget>[ - NoElements(text: 'no_transactions') - ]) - : ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - shrinkWrap: true, - controller: _transScrollController, - itemCount: transactions.length, - // Size of elements - // itemExtent: 100, - itemBuilder: (BuildContext context, int index) { - return Slidable( - // Specify a key if the Slidable is dismissible. - key: const ValueKey<int>(0), - // The end action pane is the one at the right or the bottom side. - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: <SlidableAction>[ - SlidableAction( - onPressed: (BuildContext c) { - _addContact(transactions, index, - myPubKey, contactsCubit); - // FIXME i18n - ScaffoldMessenger.of(context) - .showSnackBar( - SnackBar( - content: Text(tr('contact_added')), - ), - ); - }, - backgroundColor: - Theme.of(context).primaryColor, - foregroundColor: Colors.white, - icon: Icons.contacts, - label: tr('add_contact'), + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 50), + child: transactions.isEmpty + ? Column(children: const <Widget>[ + NoElements(text: 'no_transactions') + ]) + : ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + controller: _transScrollController, + itemCount: transactions.length, + // Size of elements + // itemExtent: 100, + itemBuilder: (BuildContext context, int index) { + return Slidable( + // Specify a key if the Slidable is dismissible. + key: const ValueKey<int>(0), + // The end action pane is the one at the right or the bottom side. + endActionPane: ActionPane( + motion: const ScrollMotion(), + children: <SlidableAction>[ + SlidableAction( + onPressed: (BuildContext c) { + _addContact(transactions, index, + myPubKey, contactsCubit); + // FIXME i18n + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: + Text(tr('contact_added')), + ), + ); + }, + backgroundColor: + Theme.of(context).primaryColor, + foregroundColor: Colors.white, + icon: Icons.contacts, + label: tr('add_contact'), + ), + ], ), - ], - ), - child: ListTile( - title: Text(tr('transaction_from_to', - namedArgs: <String, String>{ - 'from': humanizeFromToPubKey( - myPubKey, transactions[index].from), - 'to': humanizeFromToPubKey( - myPubKey, transactions[index].to) - })), - subtitle: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: <Widget>[ - if (transactions[index] - .comment - .isNotEmpty) - Text( - transactions[index].comment, - style: const TextStyle( - fontStyle: FontStyle.italic, - ), - ), - Text(humanizeTime( - transactions[index].time, - context.locale.toString())!) - ]), - tileColor: tileColor(index), - trailing: Text( - '${transactions[index].amount < 0 ? "" : "+"}${(transactions[index].amount / 100).toStringAsFixed(2)} Äž1', - style: TextStyle( - color: transactions[index].amount < 0 - ? Colors.red - : Colors.blue)), - )); - }, - )), - ) - ]), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: DraggableScrollableSheet( - initialChildSize: 0.12, - minChildSize: 0.12, - maxChildSize: 0.9, - builder: (BuildContext context, - ScrollController scrollController) => - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.inversePrimary, - border: Border.all( + child: ListTile( + title: Text(tr('transaction_from_to', + namedArgs: <String, String>{ + 'from': humanizeFromToPubKey( + myPubKey, + transactions[index].from), + 'to': humanizeFromToPubKey(myPubKey, + transactions[index].to) + })), + subtitle: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: <Widget>[ + if (transactions[index] + .comment + .isNotEmpty) + Text( + transactions[index].comment, + style: const TextStyle( + fontStyle: FontStyle.italic, + ), + ), + Text(humanizeTime( + transactions[index].time, + context.locale.toString())!) + ]), + tileColor: tileColor(index, context), + trailing: Text( + '${transactions[index].amount < 0 ? "" : "+"}${(transactions[index].amount / 100).toStringAsFixed(2)} Äž1', + style: TextStyle( + color: + transactions[index].amount < 0 + ? Colors.red + : Colors.blue)), + )); + }, + )), + ) + ]), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DraggableScrollableSheet( + controller: _draggableScrollableController, + initialChildSize: 0.12, + minChildSize: 0.12, + maxChildSize: 0.9, + builder: (BuildContext context, + ScrollController scrollController) => + Container( + decoration: BoxDecoration( color: Theme.of(context).colorScheme.inversePrimary, - width: 3), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(8), - ), - ), - child: Scrollbar( - child: ListView( - controller: scrollController, - children: <Widget>[ - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: Header( - text: 'balance', - // topPadding: 0, - )), - Padding( - padding: - const EdgeInsets.symmetric(vertical: 10.0), - child: Center( - child: Text( - '${(balance / 100).toStringAsFixed(2)} Äž1', - style: TextStyle( - fontSize: 36.0, - color: balance == 0 - ? Colors.lightBlue - : Colors.lightBlue, - fontWeight: FontWeight.bold), - )), + border: Border.all( + color: Theme.of(context) + .colorScheme + .inversePrimary, + width: 3), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ), ), - if (!kReleaseMode) TransactionChart() - /*BalanceChart( + child: Scrollbar( + child: ListView( + controller: scrollController, + children: <Widget>[ + const Padding( + padding: + EdgeInsets.symmetric(horizontal: 16), + child: Header( + text: 'balance', + // topPadding: 0, + )), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10.0), + child: Center( + child: Text( + '${formatKAmount(balance)} Äž1', + style: TextStyle( + fontSize: 36.0, + color: balance == 0 + ? Colors.lightBlue + : Colors.lightBlue, + fontWeight: FontWeight.bold), + )), + ), + if (!kReleaseMode) TransactionChart() + /*BalanceChart( transactions: .transactions),*/ - ], - )), - ))) - ]); + ], + )), + ))) + ])); } else { return const LoadingScreen(); } diff --git a/lib/ui/widgets/third_screen/contacts_page.dart b/lib/ui/widgets/third_screen/contacts_page.dart index b9589f6cd5be377f85435fd1565a2af696bf269e..19c0bdd892f7932446dc489bb02830e48ee74d6f 100644 --- a/lib/ui/widgets/third_screen/contacts_page.dart +++ b/lib/ui/widgets/third_screen/contacts_page.dart @@ -138,10 +138,10 @@ class _ContactsPageState extends State<ContactsPage> { : null, leading: avatar( contact.avatar, - bgColor: tileColor(index), - color: tileColor(index, true), + bgColor: tileColor(index, context), + color: tileColor(index, context, true), ), - tileColor: tileColor(index), + tileColor: tileColor(index, context), ), ); }, diff --git a/pubspec.lock b/pubspec.lock index e6a83fdc51864e5580e7033d6467234fda20534a..34bcdc43e160763e68ea388ba8afa506a12b2bc7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.10.0" + backdrop: + dependency: "direct main" + description: + name: backdrop + sha256: fdef5a7c1b601a20484df607d852757ebc57f4959832ba77cde5e4d736c91900 + url: "https://pub.dev" + source: hosted + version: "0.8.1" bip32_ed25519: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 33f80a245e8a0915b3a91aa2e25f04fa873d868c..b9d39b4725643211384a00f43eb3d7374ca1c0cc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,6 +62,7 @@ dependencies: timeago: ^3.3.0 once: ^1.5.1 pattern_lock: ^2.0.0 + backdrop: ^0.8.1 dev_dependencies: flutter_test: diff --git a/web/favicon.ico b/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..02c4440d0e540f7d28fa188e6a47d7c573c5c5ea Binary files /dev/null and b/web/favicon.ico differ diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..bc6aa05607752193de7656dc5420aa5d5a12069a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..e58fd125e0483446a22bd4fae74821fe5f38f732 Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f49fdf9aa9f6154a36525aca939ce1d54d103d Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..74464e32bcd52b0f6cf8c8f84c4045b31a10c81b Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..63122823a15081454802ffe5434054073033bf79 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000000000000000000000000000000000000..583fb498184d110fa735902723a6906a7139032b --- /dev/null +++ b/web/index.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<html> +<head> + <!-- + If you are serving your web app in a path other than the root, change the + href value below to reflect the base path you are serving from. + + The path provided below has to start and end with a slash "/" in order for + it to work correctly. + + For more details: + * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. + --> + <base href="$FLUTTER_BASE_HREF"> + + <meta charset="UTF-8"> + <meta content="IE=Edge" http-equiv="X-UA-Compatible"> + <meta content="Äž1nkgo" name="description"> + + <!-- iOS meta tags & icons --> + <meta name="apple-mobile-web-app-capable" content="yes"> + <meta name="apple-mobile-web-app-status-bar-style" content="black"> + <meta name="apple-mobile-web-app-title" content="Äžinkgo"> + <link rel="apple-touch-icon" href="icons/Icon-192.png"> + + <!-- Favicon --> + <link rel="icon" type="image/png" href="favicon.png"/> + + <title>Äžinkgo</title> + <link rel="manifest" href="manifest.json"> + + <style> + #splash-container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + </style> + <script> + // The value below is injected by flutter build, do not touch. + var serviceWorkerVersion = null; + </script> + <!-- This script adds the flutter initialization JS code --> + <script src="flutter.js" defer></script> +</head> +<body> +<div id="splash-container"> + <img alt="Logo" src="assets/img/logo.png"> +</div> + <script> + window.addEventListener('load', function(ev) { + // Download main.dart.js + _flutter.loader.loadEntrypoint({ + serviceWorker: { + serviceWorkerVersion: serviceWorkerVersion, + }, + onEntrypointLoaded: function(engineInitializer) { + engineInitializer.initializeEngine().then(function(appRunner) { + appRunner.runApp(); + }); + } + }); + }); + </script> +</body> +</html> diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..88c69a4a38616fdebe16349c8e768189918ffa30 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "G1nkgo", + "short_name": "G1nkgo", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Äž1nkgo (aka Ginkgo) is a lightweight Äž1 wallet", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}