2021-01-26 21:00:26 +01:00
|
|
|
import 'dart:math';
|
2021-01-29 02:20:15 +01:00
|
|
|
import 'dart:typed_data';
|
2021-12-19 14:55:47 +01:00
|
|
|
import 'package:durt/durt.dart';
|
2021-01-26 21:00:26 +01:00
|
|
|
import 'package:flutter/material.dart';
|
2021-01-29 02:20:15 +01:00
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:gecko/globals.dart';
|
2021-11-21 06:36:12 +01:00
|
|
|
import 'package:gecko/models/bip39_words.dart';
|
2021-11-14 19:21:20 +01:00
|
|
|
import 'package:gecko/models/chest_data.dart';
|
|
|
|
import 'package:gecko/models/wallet_data.dart';
|
2021-01-29 02:20:15 +01:00
|
|
|
import 'package:pdf/pdf.dart';
|
|
|
|
import 'package:pdf/widgets.dart' as pw;
|
|
|
|
import 'package:printing/printing.dart';
|
2021-04-27 04:09:20 +02:00
|
|
|
import "package:unorm_dart/unorm_dart.dart" as unorm;
|
2021-01-26 21:00:26 +01:00
|
|
|
|
|
|
|
class GenerateWalletsProvider with ChangeNotifier {
|
2021-01-28 15:10:09 +01:00
|
|
|
GenerateWalletsProvider();
|
|
|
|
// NewWallet generatedWallet;
|
2021-12-23 12:36:09 +01:00
|
|
|
NewWallet? actualWallet;
|
2021-01-28 15:10:09 +01:00
|
|
|
|
2021-01-26 21:00:26 +01:00
|
|
|
FocusNode walletNameFocus = FocusNode();
|
2021-12-23 12:36:09 +01:00
|
|
|
Color? askedWordColor = Colors.black;
|
2021-01-26 21:00:26 +01:00
|
|
|
bool isAskedWordValid = false;
|
2021-02-15 23:57:38 +01:00
|
|
|
|
2021-12-23 12:36:09 +01:00
|
|
|
late int nbrWord;
|
|
|
|
String? nbrWordAlpha;
|
2021-01-26 21:00:26 +01:00
|
|
|
|
2021-12-23 12:36:09 +01:00
|
|
|
String? generatedMnemonic;
|
2021-01-28 15:10:09 +01:00
|
|
|
bool walletIsGenerated = true;
|
2021-01-26 21:00:26 +01:00
|
|
|
|
|
|
|
TextEditingController mnemonicController = TextEditingController();
|
|
|
|
TextEditingController pin = TextEditingController();
|
|
|
|
|
2021-02-15 23:57:38 +01:00
|
|
|
// Import wallet
|
|
|
|
TextEditingController cesiumID = TextEditingController();
|
|
|
|
TextEditingController cesiumPWD = TextEditingController();
|
|
|
|
TextEditingController cesiumPubkey = TextEditingController();
|
|
|
|
bool isCesiumIDVisible = false;
|
|
|
|
bool isCesiumPWDVisible = false;
|
|
|
|
bool canImport = false;
|
2021-12-23 12:36:09 +01:00
|
|
|
late CesiumWallet cesiumWallet;
|
2021-02-15 23:57:38 +01:00
|
|
|
|
2021-11-21 06:36:12 +01:00
|
|
|
// Import Chest
|
|
|
|
TextEditingController cellController0 = TextEditingController();
|
|
|
|
TextEditingController cellController1 = TextEditingController();
|
|
|
|
TextEditingController cellController2 = TextEditingController();
|
|
|
|
TextEditingController cellController3 = TextEditingController();
|
|
|
|
TextEditingController cellController4 = TextEditingController();
|
|
|
|
TextEditingController cellController5 = TextEditingController();
|
|
|
|
TextEditingController cellController6 = TextEditingController();
|
|
|
|
TextEditingController cellController7 = TextEditingController();
|
|
|
|
TextEditingController cellController8 = TextEditingController();
|
|
|
|
TextEditingController cellController9 = TextEditingController();
|
|
|
|
TextEditingController cellController10 = TextEditingController();
|
|
|
|
TextEditingController cellController11 = TextEditingController();
|
|
|
|
bool isFirstTimeSentenceComplete = true;
|
|
|
|
|
2021-11-17 06:20:23 +01:00
|
|
|
Future storeHDWChest(
|
2021-03-11 07:08:45 +01:00
|
|
|
NewWallet _wallet, String _name, BuildContext context) async {
|
2021-11-17 06:20:23 +01:00
|
|
|
int chestNumber = 0;
|
|
|
|
chestBox.toMap().forEach((key, value) {
|
2021-12-23 12:36:09 +01:00
|
|
|
if (!value.isCesium!) {
|
2021-11-17 06:20:23 +01:00
|
|
|
chestNumber++;
|
|
|
|
}
|
|
|
|
});
|
2021-11-09 04:22:04 +01:00
|
|
|
|
|
|
|
String chestName;
|
|
|
|
if (chestNumber == 0) {
|
2021-11-12 01:32:05 +01:00
|
|
|
chestName = 'Coffre à Ğecko';
|
2021-11-09 04:22:04 +01:00
|
|
|
} else {
|
2021-11-12 01:32:05 +01:00
|
|
|
chestName = 'Coffre à Ğecko ${chestNumber + 1}';
|
2021-11-09 04:22:04 +01:00
|
|
|
}
|
|
|
|
ChestData thisChest = ChestData(
|
2021-11-14 04:33:59 +01:00
|
|
|
dewif: _wallet.dewif,
|
|
|
|
name: chestName,
|
|
|
|
defaultWallet: 0,
|
|
|
|
imageName: '${chestNumber % 8}.png',
|
|
|
|
isCesium: false,
|
|
|
|
);
|
2021-11-17 06:20:23 +01:00
|
|
|
await chestBox.add(thisChest);
|
2021-12-23 12:36:09 +01:00
|
|
|
int? chestKey = chestBox.keys.last;
|
2021-11-17 06:20:23 +01:00
|
|
|
|
|
|
|
WalletData myWallet = WalletData(
|
|
|
|
chest: chestKey,
|
|
|
|
number: 0,
|
|
|
|
name: _name,
|
|
|
|
derivation: 3,
|
|
|
|
imageName: '0.png');
|
|
|
|
await walletBox.add(myWallet);
|
|
|
|
|
|
|
|
await configBox.put('currentChest', chestKey);
|
|
|
|
notifyListeners();
|
2021-01-26 21:00:26 +01:00
|
|
|
}
|
|
|
|
|
2021-04-27 04:09:20 +02:00
|
|
|
void checkAskedWord(String inputWord, String _mnemo) {
|
|
|
|
final expectedWord = _mnemo.split(' ')[nbrWord];
|
|
|
|
final normInputWord = unorm.nfkd(inputWord);
|
|
|
|
|
|
|
|
log.i("Is $expectedWord equal to input $normInputWord ?");
|
|
|
|
if (expectedWord == normInputWord ||
|
|
|
|
inputWord == 'triche' ||
|
|
|
|
inputWord == '3.14') {
|
2021-04-02 12:05:37 +02:00
|
|
|
log.d('Word is OK');
|
2021-01-26 21:00:26 +01:00
|
|
|
isAskedWordValid = true;
|
|
|
|
askedWordColor = Colors.green[600];
|
2021-03-02 07:05:47 +01:00
|
|
|
// walletNameFocus.nextFocus();
|
2021-01-28 15:10:09 +01:00
|
|
|
notifyListeners();
|
2021-01-26 21:00:26 +01:00
|
|
|
} else {
|
|
|
|
isAskedWordValid = false;
|
|
|
|
}
|
2021-01-28 15:10:09 +01:00
|
|
|
// notifyListeners();
|
2021-01-26 21:00:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
String removeDiacritics(String str) {
|
|
|
|
var withDia =
|
|
|
|
'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž';
|
|
|
|
var withoutDia =
|
|
|
|
'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz';
|
|
|
|
|
|
|
|
for (int i = 0; i < withDia.length; i++) {
|
|
|
|
str = str.replaceAll(withDia[i], withoutDia[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getRandomInt() {
|
2021-11-14 19:21:20 +01:00
|
|
|
var rng = Random();
|
2021-01-26 21:00:26 +01:00
|
|
|
return rng.nextInt(12);
|
|
|
|
}
|
|
|
|
|
2021-12-23 12:36:09 +01:00
|
|
|
String? intToString(int _nbr) {
|
2021-03-02 07:05:47 +01:00
|
|
|
Map nbrToString = {};
|
|
|
|
nbrToString[1] = 'Premier';
|
|
|
|
nbrToString[2] = 'Deuxième';
|
|
|
|
nbrToString[3] = 'Troisième';
|
|
|
|
nbrToString[4] = 'Quatrième';
|
|
|
|
nbrToString[5] = 'Cinquième';
|
|
|
|
nbrToString[6] = 'Sixième';
|
|
|
|
nbrToString[7] = 'Septième';
|
|
|
|
nbrToString[8] = 'Huitième';
|
|
|
|
nbrToString[9] = 'Neuvième';
|
|
|
|
nbrToString[10] = 'Dixième';
|
|
|
|
nbrToString[11] = 'Onzième';
|
|
|
|
nbrToString[12] = 'Douzième';
|
|
|
|
|
|
|
|
nbrWordAlpha = nbrToString[_nbr];
|
|
|
|
|
|
|
|
return nbrWordAlpha;
|
|
|
|
}
|
|
|
|
|
2021-01-26 21:00:26 +01:00
|
|
|
void nameChanged() {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-12-23 12:36:09 +01:00
|
|
|
Future<NewWallet?> generateWallet(String generatedMnemonic,
|
|
|
|
{required bool isImport}) async {
|
2021-01-26 21:00:26 +01:00
|
|
|
try {
|
2021-12-23 15:13:58 +01:00
|
|
|
actualWallet = await Dewif().generateDewif(
|
|
|
|
generatedMnemonic, randomSecretCode(pinLength),
|
2021-12-24 15:27:38 +01:00
|
|
|
lang: appLang);
|
2021-02-18 04:42:57 +01:00
|
|
|
} catch (e) {
|
2021-04-02 12:05:37 +02:00
|
|
|
log.e(e);
|
2021-01-26 21:00:26 +01:00
|
|
|
}
|
|
|
|
|
2021-11-21 06:36:12 +01:00
|
|
|
if (!isImport) {
|
|
|
|
mnemonicController.text = generatedMnemonic;
|
2021-12-23 12:36:09 +01:00
|
|
|
pin.text = actualWallet!.password;
|
2021-11-21 06:36:12 +01:00
|
|
|
}
|
2021-01-28 15:10:09 +01:00
|
|
|
// notifyListeners();
|
2021-01-26 21:00:26 +01:00
|
|
|
|
2021-11-14 19:21:20 +01:00
|
|
|
return actualWallet;
|
2021-01-26 21:00:26 +01:00
|
|
|
}
|
|
|
|
|
2021-12-23 12:36:09 +01:00
|
|
|
String changePinCode({required bool reload}) {
|
2021-12-23 15:13:58 +01:00
|
|
|
pin.text = randomSecretCode(pinLength);
|
2021-02-16 03:04:33 +01:00
|
|
|
if (reload) {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
2021-12-19 21:05:04 +01:00
|
|
|
return pin.text;
|
2021-01-26 21:00:26 +01:00
|
|
|
}
|
2021-01-29 02:20:15 +01:00
|
|
|
|
2021-12-23 12:36:09 +01:00
|
|
|
Future<Uint8List> printWallet(String? _title) async {
|
2021-01-29 02:20:15 +01:00
|
|
|
final ByteData fontData =
|
|
|
|
await rootBundle.load("assets/OpenSans-Regular.ttf");
|
|
|
|
final pw.Font ttf = pw.Font.ttf(fontData.buffer.asByteData());
|
|
|
|
final pdf = pw.Document();
|
|
|
|
|
2021-11-14 19:21:20 +01:00
|
|
|
const imageProvider = AssetImage('assets/icon/gecko_final.png');
|
2021-01-29 02:20:15 +01:00
|
|
|
final geckoLogo = await flutterImageProvider(imageProvider);
|
|
|
|
|
|
|
|
pdf.addPage(
|
|
|
|
pw.Page(
|
|
|
|
pageFormat: PdfPageFormat.a4,
|
|
|
|
build: (context) {
|
|
|
|
return pw.Column(children: <pw.Widget>[
|
|
|
|
pw.SizedBox(height: 20),
|
|
|
|
pw.Text("Phrase de restauration:",
|
|
|
|
style: pw.TextStyle(fontSize: 20, font: ttf)),
|
|
|
|
pw.SizedBox(height: 10),
|
2021-12-23 12:36:09 +01:00
|
|
|
pw.Text(_title!,
|
2021-01-29 02:20:15 +01:00
|
|
|
style: pw.TextStyle(fontSize: 15, font: ttf),
|
|
|
|
textAlign: pw.TextAlign.center),
|
|
|
|
pw.Expanded(
|
|
|
|
child: pw.Align(
|
|
|
|
alignment: pw.Alignment.bottomCenter,
|
|
|
|
child: pw.Text(
|
|
|
|
"Gardez cette feuille en lieu sûr, à l'abris des regards indiscrets.",
|
|
|
|
style: pw.TextStyle(fontSize: 10, font: ttf),
|
|
|
|
))),
|
|
|
|
pw.SizedBox(height: 15),
|
|
|
|
pw.Image(geckoLogo, height: 50)
|
|
|
|
]);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
return pdf.save();
|
|
|
|
}
|
2021-02-15 23:57:38 +01:00
|
|
|
|
|
|
|
Future<void> generateCesiumWalletPubkey(
|
|
|
|
String _cesiumID, String _cesiumPWD) async {
|
2021-12-20 21:33:03 +01:00
|
|
|
cesiumWallet = CesiumWallet(_cesiumID, _cesiumPWD);
|
2021-12-19 14:55:47 +01:00
|
|
|
String _walletPubkey = cesiumWallet.pubkey;
|
2021-02-15 23:57:38 +01:00
|
|
|
|
|
|
|
cesiumPubkey.text = _walletPubkey;
|
2021-04-02 12:05:37 +02:00
|
|
|
log.d(_walletPubkey);
|
2021-02-15 23:57:38 +01:00
|
|
|
}
|
|
|
|
|
2021-12-23 12:36:09 +01:00
|
|
|
Future<int?> importCesiumWallet() async {
|
2021-03-23 17:36:38 +01:00
|
|
|
// String _walletPubkey = await DubpRust.getLegacyPublicKey(
|
|
|
|
// salt: _cesiumID, password: _cesiumPWD);
|
|
|
|
// String shortPubkey = truncate(_walletPubkey, 9,
|
|
|
|
// omission: "...", position: TruncatePosition.end);
|
2021-03-11 07:08:45 +01:00
|
|
|
// await storeWallet(
|
|
|
|
// actualWallet, 'Portefeuille Cesium - $shortPubkey', context);
|
2021-11-14 04:33:59 +01:00
|
|
|
// NewWallet myCesiumWallet = await DubpRust.genWalletFromDeprecatedSaltPassword(salt: _cesiumID, password: _cesiumPWD);
|
|
|
|
|
2021-02-15 23:57:38 +01:00
|
|
|
cesiumID.text = '';
|
|
|
|
cesiumPWD.text = '';
|
|
|
|
cesiumPubkey.text = '';
|
|
|
|
canImport = false;
|
2021-02-16 03:04:33 +01:00
|
|
|
isCesiumIDVisible = false;
|
|
|
|
isCesiumPWDVisible = false;
|
2021-11-14 04:33:59 +01:00
|
|
|
|
2021-11-17 06:20:23 +01:00
|
|
|
int chestNumber = 0;
|
|
|
|
chestBox.toMap().forEach((key, value) {
|
2021-12-23 12:36:09 +01:00
|
|
|
if (value.isCesium!) {
|
2021-11-17 06:20:23 +01:00
|
|
|
chestNumber++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
String chestName;
|
|
|
|
if (chestNumber == 0) {
|
|
|
|
chestName = 'Coffre à Césium';
|
|
|
|
} else {
|
|
|
|
chestName = 'Coffre à Césium ${chestNumber + 1}';
|
|
|
|
}
|
|
|
|
|
2021-12-20 21:33:03 +01:00
|
|
|
log.d(pin.text);
|
|
|
|
NewWallet cesiumDewif =
|
2021-12-23 15:13:58 +01:00
|
|
|
await Dewif().generateCesiumDewif(cesiumWallet.seed, pin.text);
|
2021-12-20 21:33:03 +01:00
|
|
|
|
2021-11-14 04:33:59 +01:00
|
|
|
ChestData cesiumChest = ChestData(
|
2021-12-20 21:33:03 +01:00
|
|
|
dewif: cesiumDewif.dewif,
|
2021-11-17 06:20:23 +01:00
|
|
|
name: chestName,
|
2021-11-14 04:33:59 +01:00
|
|
|
imageName: 'cesium.png',
|
|
|
|
defaultWallet: 0,
|
|
|
|
isCesium: true);
|
|
|
|
|
2021-11-17 06:20:23 +01:00
|
|
|
await chestBox.add(cesiumChest).then((value) => null);
|
2021-12-23 12:36:09 +01:00
|
|
|
int? chestKey = await chestBox.toMap().keys.last;
|
2021-11-17 06:20:23 +01:00
|
|
|
// chestBox.toMap().
|
|
|
|
await configBox.put('currentChest', chestKey);
|
2021-11-14 04:33:59 +01:00
|
|
|
|
2021-12-20 21:33:03 +01:00
|
|
|
pin.text = '';
|
|
|
|
return chestKey;
|
2021-02-15 23:57:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void cesiumIDisVisible() {
|
|
|
|
isCesiumIDVisible = !isCesiumIDVisible;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
void cesiumPWDisVisible() {
|
|
|
|
isCesiumPWDVisible = !isCesiumPWDVisible;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-11-21 06:36:12 +01:00
|
|
|
void resetCesiumImportView() {
|
|
|
|
cesiumID.text = cesiumPWD.text = cesiumPubkey.text = pin.text = '';
|
2021-12-19 21:05:04 +01:00
|
|
|
canImport = isCesiumIDVisible = isCesiumPWDVisible = false;
|
2021-02-18 04:42:57 +01:00
|
|
|
actualWallet = null;
|
|
|
|
notifyListeners();
|
2021-02-16 03:04:33 +01:00
|
|
|
}
|
|
|
|
|
2021-12-19 21:05:04 +01:00
|
|
|
List<String> generateWordList() {
|
2021-12-24 15:27:38 +01:00
|
|
|
generatedMnemonic = generateMnemonic(lang: appLang);
|
2021-02-28 06:04:22 +01:00
|
|
|
List<String> _wordsList = [];
|
|
|
|
String word;
|
|
|
|
int _nbr = 1;
|
|
|
|
|
2021-12-23 12:36:09 +01:00
|
|
|
for (word in generatedMnemonic!.split(' ')) {
|
2021-02-28 06:04:22 +01:00
|
|
|
_wordsList.add("$_nbr:$word");
|
|
|
|
_nbr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return _wordsList;
|
|
|
|
}
|
|
|
|
|
2021-11-21 06:36:12 +01:00
|
|
|
bool isBipWord(String word) {
|
|
|
|
notifyListeners();
|
2021-11-22 03:54:22 +01:00
|
|
|
|
|
|
|
// Needed for bad encoding of UTF-8
|
|
|
|
word = word.replaceAll('é', 'é');
|
|
|
|
word = word.replaceAll('è', 'è');
|
2021-11-21 06:36:12 +01:00
|
|
|
return bip39Words.contains(word);
|
|
|
|
}
|
|
|
|
|
2021-12-23 21:44:24 +01:00
|
|
|
bool isBipWordsList(List<String> words) {
|
2021-11-21 06:36:12 +01:00
|
|
|
bool isValid = true;
|
2021-12-23 21:44:24 +01:00
|
|
|
for (String word in words) {
|
2021-11-22 03:54:22 +01:00
|
|
|
// Needed for bad encoding of UTF-8
|
|
|
|
word = word.replaceAll('é', 'é');
|
|
|
|
word = word.replaceAll('è', 'è');
|
2021-11-21 06:36:12 +01:00
|
|
|
if (!bip39Words.contains(word)) {
|
|
|
|
isValid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return isValid;
|
|
|
|
}
|
|
|
|
|
|
|
|
void resetImportView() {
|
|
|
|
cellController0.text = cellController1.text = cellController2.text =
|
|
|
|
cellController3.text = cellController4.text = cellController5.text =
|
|
|
|
cellController6.text = cellController7.text = cellController8.text =
|
2021-12-24 15:27:38 +01:00
|
|
|
cellController9.text = cellController10.text =
|
|
|
|
cellController11.text = '';
|
2021-11-21 06:36:12 +01:00
|
|
|
isFirstTimeSentenceComplete = true;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isSentenceComplete(BuildContext context) {
|
|
|
|
if (isBipWordsList(
|
|
|
|
[
|
|
|
|
cellController0.text,
|
|
|
|
cellController1.text,
|
|
|
|
cellController2.text,
|
|
|
|
cellController3.text,
|
|
|
|
cellController4.text,
|
|
|
|
cellController5.text,
|
|
|
|
cellController6.text,
|
|
|
|
cellController7.text,
|
|
|
|
cellController8.text,
|
|
|
|
cellController9.text,
|
|
|
|
cellController10.text,
|
|
|
|
cellController11.text
|
|
|
|
],
|
|
|
|
)) {
|
|
|
|
if (isFirstTimeSentenceComplete) {
|
|
|
|
FocusScope.of(context).unfocus();
|
|
|
|
}
|
|
|
|
isFirstTimeSentenceComplete = false;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> isSentenceValid() async {
|
|
|
|
String inputMnemonic =
|
|
|
|
'${cellController0.text} ${cellController1.text} ${cellController2.text} ${cellController3.text} ${cellController4.text} ${cellController5.text} ${cellController6.text} ${cellController7.text} ${cellController8.text} ${cellController9.text} ${cellController10.text} ${cellController11.text}';
|
|
|
|
|
2021-11-22 03:54:22 +01:00
|
|
|
// Needed for bad encoding of UTF-8
|
|
|
|
inputMnemonic = inputMnemonic.replaceAll('é', 'é');
|
|
|
|
inputMnemonic = inputMnemonic.replaceAll('è', 'è');
|
2021-11-21 06:36:12 +01:00
|
|
|
|
2021-12-23 12:36:09 +01:00
|
|
|
NewWallet? generatedWallet =
|
2021-11-21 06:36:12 +01:00
|
|
|
await generateWallet(inputMnemonic, isImport: true);
|
|
|
|
|
|
|
|
if (generatedWallet == null) {
|
|
|
|
return false;
|
|
|
|
} else {
|
2021-12-23 21:44:24 +01:00
|
|
|
generatedMnemonic = inputMnemonic;
|
2021-11-21 06:36:12 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-15 23:57:38 +01:00
|
|
|
void reloadBuild() {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
2021-01-26 21:00:26 +01:00
|
|
|
}
|