Author Dex_DQT
In this chapter we will complete the import of the helper/secret key/wallet file and build the functionality to store them as a file
Import Stateful Widget
The code is placed in the import_stateful_widget.dart file under the app/page package.
Since the TabBarView has a lag in accepting page change callbacks when sliding the page, I use the PageView to implement the page switch function. The code is as follows:
@override void initState() { super.initState(); _tabController = TabController(length: _tabs.length, vsync: this); _tabController.addListener(() { // 点击tabBar,修改PageView当前显示的页面 if (_tabController.indexIsChanging) { _onTabPageChange(_tabController.index, p: _pageController); } }); // 监听页面变动是事件 _pageController.addListener(() { // 日后tabBar正在滑动中忽略 if (!_tabController.indexIsChanging) { if ((_pageController.page! - _tabController.index).abs() > 1.0) { _tabController.index = _pageController.page!.round(); } _tabController.offset = (_pageController.page! - _tabController.index).clamp(-1.0, 1.0); } }); }
In order to keep the page as consistent as possible with Android, I customize the TabBar’s indicator.The code is as follows:
class LineTabIndicator extends Decoration { final BorderSide borderSide; final EdgeInsetsGeometry insets; const LineTabIndicator( {this.borderSide = const BorderSide(width: 2.0, color: Color(0xff105CFF)), this.insets = EdgeInsets.zero}); @override _LineTabIndicatorPainter createBoxPainter([VoidCallback? onChanged]) { return _LineTabIndicatorPainter(this, onChanged); } } class _LineTabIndicatorPainter extends BoxPainter { final LineTabIndicator decoration; BorderSide get borderSide => decoration.borderSide; EdgeInsetsGeometry get insets => decoration.insets; late Paint _paint; _LineTabIndicatorPainter(this.decoration, VoidCallback? onChanged) : super(onChanged) { // 设置paint为圆角 _paint = borderSide.toPaint()..strokeCap = StrokeCap.round; } @override void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { assert(configuration.size != null); assert(configuration.textDirection != null); final Rect rect = offset & configuration.size!; // 获取indicator的边界 final TextDirection textDirection = configuration.textDirection!; final Rect indicatorRect = insets.resolve(textDirection).deflateRect(rect); canvas.drawLine(indicatorRect.bottomLeft, indicatorRect.bottomRight, _paint); } }
Line rounding can be achieved by drawing your own underline.
The final page will look like this:
Import Mnemonic Stateful Widget
Creat import_mnemonic_stateful_widget.dart file in app/page, with the code as follows:
import 'package:digging/app/service/wallet_manager.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'back_mnemonic_stateful_widget.dart'; import 'build_wallet_info_stateful_widget.dart'; class ImportMnemonicStatefulWidget extends StatefulWidget { const ImportMnemonicStatefulWidget({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _ImportMnemonicStatefulWidgetState(); } } class _ImportMnemonicStatefulWidgetState extends State { final WalletInfoController _controller = WalletInfoController(); // 创建textField对应的controller final List<TextEditingController> _mnemonicWorldControllers = List.generate(12, (index) => TextEditingController()); bool _isMnemoicEnable = false; bool _bShowMnemoicError = false; @override void initState() { super.initState(); _controller.addListener(() async { WalletInfo walletInfo = _controller.value; List<String> mnemonicWorlds = _getMnemonic(); bool bSucceed = await WalletManager.importMnemonicWords( walletInfo.walletName, walletInfo.password, mnemonicWorlds); if (bSucceed) { Fluttertoast.showToast(msg: "导入助记词成功!"); } else { Fluttertoast.showToast(msg: "导入助记词失败:请检查助记词是否正确!"); } }); _setWordWatcher(); } List<String> _getMnemonic() { List<String> mnemonicWorlds = List.empty(growable: true); for (int i = 0; i < 12; ++i) { mnemonicWorlds.add(_mnemonicWorldControllers[i].value.text); } return mnemonicWorlds; } /// 设置助记词监听 void _setWordWatcher() { for (var element in _mnemonicWorldControllers) { element.addListener(() { List<String> mnemonicWorlds = _getMnemonic(); for (String mnemonic in mnemonicWorlds) { if (mnemonic.isEmpty) { setState(() { _bShowMnemoicError = true; _isMnemoicEnable = false; }); return; } } setState(() { _bShowMnemoicError = false; _isMnemoicEnable = true; }); }); } } @override Widget build(BuildContext context) { return Scaffold( body: SingleChildScrollView( child: Container( padding: const EdgeInsets.only( top: 10, left: 16, right: 16, bottom: 20), decoration: const BoxDecoration(color: Colors.white), child: SizedBox( width: double.infinity, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "请将记下的助记词按顺序填入以下表格中,在每个助记词输入完成后按“空格”键可以跳转到下个单词,使用助记词导入的同时需要设定钱包密码。", style: TextStyle( fontSize: 12, color: Color(0xff61646e), ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 222, child: GridView.count( padding: EdgeInsets.zero, crossAxisCount: 3, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0, childAspectRatio: 2.4, children: List.generate( 12, (index) => Container( padding: const EdgeInsets.only( left: 5, right: 5), decoration: const BoxDecoration( color: Color(0xfff0f1f5)), child: TextField( controller: _mnemonicWorldControllers[index], textAlign: TextAlign.center, decoration: InputDecoration( enabledBorder: const UnderlineInputBorder( borderSide: BorderSide( color: Colors.transparent)), focusedBorder: const UnderlineInputBorder( borderSide: BorderSide( color: Colors.transparent)), hintStyle: const TextStyle( color: Color(0xffB8BDD2), fontSize: 14), hintText: (index + 1).toString(), ), ))))), SizedBox( height: _bShowMnemoicError ? 30 : 0, child: const Text( "助记词不能有空!", style: TextStyle(color: Colors.red), ), ), SizedBox( height: _bShowMnemoicError ? 0 : 10, ), BuildWalletInfoStatefulWidget( controller: _controller, enableFlag: _isMnemoicEnable, buttonName: "开始导入", ), ], ), )))); } }
Extracts the wallet name, password, confirmation password and other functions from the previous chapter into the Build Wallet Info Stateful Widget. Creat build_wallet_info_stateful_widget.dart in app/page. The code is as follows:
BuildWalletInfoStatefulWidget( controller: _controller, enableFlag: _isMnemoicEnable, buttonName: "开始导入", ),
The controller needs to be passed in to listen for a click on the import button, to wrap the button to be clickable and to determine if the helper is fully filled in. This can be controlled via the enableFlag parameter. The code to listen for this is in the initStatus, as follows:
_controller.addListener(() async { WalletInfo walletInfo = _controller.value; List<String> mnemonicWorlds = _getMnemonic(); bool bSucceed = await WalletManager.importMnemonicWords( walletInfo.walletName, walletInfo.password, mnemonicWorlds); if (bSucceed) { Fluttertoast.showToast(msg: "导入助记词成功!"); } else { Fluttertoast.showToast(msg: "导入助记词失败:请检查助记词是否正确!"); } });
The effect is shown as below:
Import Keystore Stateful Widget
Create the import_keystore_stateful_widget.dart file in the app/page package. The layout of this page is basically the same as that of import the Mnemonic Phrase, so you can refer to its source code.
The effect is shown as below:
Import Private Key Stateful Widget
Create the import_privatekey_stateful_widget.dart file in the app/page package. The layout of this page is basically the same as that of import the Mnemonic Phrase, so you can refer to its source code.
The effect is shown as below:
Logic of Wallet Manager Import
Import Mnemonic Words
/// static Future<bool> importMnemonicWords( String name, String password, List<String> mnemonicWords) async { /// 把助记词以空格符拼接在一起 String mnemonic = mnemonicWords.join(" "); /// 创建密钥对 ECKeyPair? ecKeyPair = WalletUtil.generateHDByMnemonic(mnemonic); if (ecKeyPair == null) { return false; } /// 保存密钥对 return _storeECKeyPair(name, password, ecKeyPair); }
Import Key Store
Here you need to verify that weather the password is correct
static Future<bool> importKeyStore( String name, String password, String keystore) async { try { /// 先用密码解密钱包文件, 看是否可以解密, 如果密码正确则给导入,否则报错 Wallet wallet = Wallet.fromJson(keystore, password); return _storeECKeyPair(name, password, wallet.keyPair); } catch (e) { return false; } }
Import Private Key
static Future<bool> importPrivateKey( String name, String password, String privateKey) async { ECKeyPair? ecKeyPair = ECKeyPair.createByHexPrivateKey(Numeric.cleanHexPrefix(privateKey)); return _storeECKeyPair(name, password, ecKeyPair); }
Store ECKey Pair
static Future<bool> _storeECKeyPair( String name, String password, ECKeyPair ecKeyPair) async { String keystore = Wallet.createNew(ecKeyPair, password).toJson(); Directory appDocDir = await getApplicationDocumentsDirectory(); String appDocPath = appDocDir.path; File file = File("$appDocPath/$name.json"); await file.writeAsString(keystore); return true; }
Here I used the path_provider package to import the file. The final file is saved as follows:
{ "address":"lat1y79negr73wnnl3rgzeyer4723akw4ydcfn567t", "crypto":{ "cipher":"aes-128-ctr", "cipherparams":{ "iv":"661f165aab5f019ecfaae097e299cb57" }, "ciphertext":"e4c11c0a3e7f44caa6033df1fcebddc288a08c7ca76dde332b1a511c849a12df", "kdf":"scrypt", "kdfparams":{ "dklen":32, "n":8192, "r":8, "p":1, "salt":"24a41c352440cfcd59cd6898fb4cfcc9341fd0e9357ab69c7b67512dc9a63a75" }, "mac":"25eba08a93064305efae95a8b4337c1c59d407554078d6aa3e5478430088d45e" }, "id":"cc2e28e1-17d5-4bd7-9e5d-ce4d3539f618", "version":3 }
Here I have only saved the address of mainnet.
Note: The Wallet function is based on web3dart.
Well, that’s it for this chapter, in the next chapter we’ll start building the main page.
The URL of github: https://github.com/DQTechnology/Platon_DevGuideProject
This article is reproduced from https://forum.latticex.foundation/t/topic/6046