From a755ed244365f4cf2999b662ef55a743d69254dd Mon Sep 17 00:00:00 2001 From: Sun Date: Sat, 9 Oct 2021 12:40:59 +0800 Subject: [PATCH] Add check register to default domain after genereate account & add quick create account --- lib/l10n/localizations.dart | 2 + lib/l10n/localizations_en.dart | 4 + lib/l10n/localizations_zh.dart | 4 + lib/pages/account_domain.dart | 169 +++++++++++++++++++++++ lib/pages/account_generate.dart | 19 +-- lib/pages/account_quick.dart | 234 ++++++++++++++++++++++++++++++++ lib/pages/account_restore.dart | 7 +- lib/pages/setting/profile.dart | 16 ++- lib/security.dart | 81 +++++++---- lib/utils/mnemonic.dart | 12 ++ 10 files changed, 501 insertions(+), 47 deletions(-) create mode 100644 lib/pages/account_domain.dart create mode 100644 lib/pages/account_quick.dart diff --git a/lib/l10n/localizations.dart b/lib/l10n/localizations.dart index d7c5ca7..1e65183 100644 --- a/lib/l10n/localizations.dart +++ b/lib/l10n/localizations.dart @@ -95,6 +95,7 @@ abstract class AppLocalizations { String get input; String get waiting; String get notExist; + String get skip; // theme String get themeDark; @@ -108,6 +109,7 @@ abstract class AppLocalizations { String get loginRestore; String get loginRestoreOnline; String get loginNew; + String get loginQuick; String get newMnemonicTitle; String get newMnemonicInput; String get hasAccount; diff --git a/lib/l10n/localizations_en.dart b/lib/l10n/localizations_en.dart index ae57492..e52c390 100644 --- a/lib/l10n/localizations_en.dart +++ b/lib/l10n/localizations_en.dart @@ -114,6 +114,8 @@ class AppLocalizationsEn extends AppLocalizations { String get waiting => 'Waiting'; @override String get notExist => 'User not exist.'; + @override + String get skip => 'Skip'; // theme @override @@ -135,6 +137,8 @@ class AppLocalizationsEn extends AppLocalizations { @override String get loginNew => 'Create Account'; @override + String get loginQuick => 'Quickly Create'; + @override String get newMnemonicTitle => 'Mnemonic code (DID)'; @override String get newMnemonicInput => 'Generate'; diff --git a/lib/l10n/localizations_zh.dart b/lib/l10n/localizations_zh.dart index 1863b36..032f106 100644 --- a/lib/l10n/localizations_zh.dart +++ b/lib/l10n/localizations_zh.dart @@ -114,6 +114,8 @@ class AppLocalizationsZh extends AppLocalizations { String get waiting => '等待中'; @override String get notExist => '用户不存在。'; + @override + String get skip => '跳过'; // theme @override @@ -135,6 +137,8 @@ class AppLocalizationsZh extends AppLocalizations { @override String get loginNew => '新建账户'; @override + String get loginQuick => '快速新建'; + @override String get newMnemonicTitle => '助记词(DID)'; @override String get newMnemonicInput => '生成'; diff --git a/lib/pages/account_domain.dart b/lib/pages/account_domain.dart new file mode 100644 index 0000000..5d39356 --- /dev/null +++ b/lib/pages/account_domain.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; + +import 'package:esse/l10n/localizations.dart'; +import 'package:esse/widgets/button_text.dart'; +import 'package:esse/rpc.dart'; +import 'package:esse/apps/domain/models.dart'; + +class AccountDomainScreen extends StatefulWidget { + final String name; + + const AccountDomainScreen({Key? key, required this.name}) : super(key: key); + + @override + AccountDomainScreenState createState() => AccountDomainScreenState(); +} + +class AccountDomainScreenState extends State { + Map _providers = {}; + + int _selected = -1; + bool _showName = false; + bool _exist = false; + bool _waiting = false; + + TextEditingController _nameController = new TextEditingController(); + FocusNode _nameFocus = new FocusNode(); + + _domainList(List params) { + this._providers.clear(); + params[0].forEach((param) { + this._providers[param[0]] = ProviderServer.fromList(param); + }); + setState(() {}); + } + + _domainRegisterSuccess(List params) { + // TODO toast. + Navigator.of(context).pushNamedAndRemoveUntil("/", (Route route) => false); + } + + _domainRegisterFailure(List _params) { + setState(() { + this._waiting = false; + this._exist = true; + this._showName = true; + }); + } + + @override + initState() { + super.initState(); + + rpc.addListener('domain-list', _domainList, false); + rpc.addListener('domain-register-success', _domainRegisterSuccess, false); + rpc.addListener('domain-register-failure', _domainRegisterFailure, false); + rpc.send('domain-list', []); + + _nameController.text = widget.name; + _nameFocus.addListener(() { + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme; + final lang = AppLocalizations.of(context); + + if (this._selected < 0 && this._providers.length > 0) { + this._selected = 0; + this._providers.forEach((k, v) { + if (v.isDefault) { + this._selected = k; + } + }); + } + + final provider = this._providers.length > 0 + ? this._providers[this._selected]! : ProviderServer.empty(); + + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + RichText( + text: TextSpan( + text: 'Register ', + style: TextStyle( + color: color.onSurface, fontSize: 20.0, fontWeight: FontWeight.bold + ), + children: [ + TextSpan(text: widget.name, style: TextStyle(color: Color(0xFF6174FF))), + TextSpan(text: ' to '), + TextSpan(text: provider.name, + style: TextStyle(color: Color(0xFF6174FF), fontStyle: FontStyle.italic) + ), + TextSpan(text: ' ?'), + ], + ), + ), + const SizedBox(height: 10.0), + Container( + width: 600.0, + child: Text('Tips: It will be sent to our built-in provider. Others can find your information (avatar, nickname, ESSEID, network address) by search the username, which can be managed and deleted in the personal ID later.'), + ), + SizedBox( + height: 40.0, + child: Center(child: Text(this._exist ? lang.domainRegisterFailure : '', + style: TextStyle(color: Colors.red))), + ), + if (this._showName) + Container( + width: 600.0, + height: 50.0, + padding: const EdgeInsets.symmetric(horizontal: 20.0), + decoration: BoxDecoration( + color: color.surface, + border: Border.all( + color: _nameFocus.hasFocus ? color.primary : color.surface), + borderRadius: BorderRadius.circular(10.0), + ), + child: TextField( + style: TextStyle(fontSize: 16.0), + decoration: InputDecoration( + border: InputBorder.none, + hintText: lang.domainName, + ), + controller: _nameController, + focusNode: _nameFocus + ), + ), + const SizedBox(height: 20.0), + ButtonText( + enable: this._providers.length > 0 && !this._waiting, + text: this._waiting ? lang.waiting : lang.send, + action: () { + final name = _nameController.text.trim(); + if (name.length > 0) { + rpc.send('domain-register', [provider.id, provider.addr, name, '']); + setState(() { + this._waiting = true; + this._exist = false; + }); + } + }), + const SizedBox(height: 20.0), + InkWell( + child: Container( + width: 600.0, + height: 50.0, + decoration: BoxDecoration( + border: Border.all(color: Color(0xFF6174FF)), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Text(lang.skip, style: TextStyle( + fontSize: 20.0, color: Color(0xFF6174FF) + ))), + ), + onTap: () { + Navigator.of(context).pushNamedAndRemoveUntil("/", (Route route) => false); + } + ), + ] + ) + ) + ); + } +} diff --git a/lib/pages/account_generate.dart b/lib/pages/account_generate.dart index 2adfdf7..1df5129 100644 --- a/lib/pages/account_generate.dart +++ b/lib/pages/account_generate.dart @@ -17,6 +17,7 @@ import 'package:esse/global.dart'; import 'package:esse/rpc.dart'; import 'package:esse/provider.dart'; +import 'package:esse/pages/account_domain.dart'; import 'package:esse/apps/device/provider.dart'; import 'package:esse/apps/chat/provider.dart'; import 'package:esse/apps/group_chat/provider.dart'; @@ -44,6 +45,7 @@ class _AccountGeneratePageState extends State { @override initState() { super.initState(); + _nameFocus.addListener(() { setState(() {}); }); @@ -103,7 +105,9 @@ class _AccountGeneratePageState extends State { Provider.of(context, listen: false).updateActived(); Provider.of(context, listen: false).updateActived(); - Navigator.of(context).pushNamedAndRemoveUntil("/", (Route route) => false); + Navigator.push(context, MaterialPageRoute(builder: (_) => AccountDomainScreen( + name: name, + ))); } else { // TODO tostor error print(res.error); @@ -142,7 +146,7 @@ class _AccountGeneratePageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - width: 450.0, + width: 600.0, child: Row(children: [ Expanded( child: Container( @@ -197,7 +201,7 @@ class _AccountGeneratePageState extends State { ])), const SizedBox(height: 32.0), Container( - width: 450.0, + width: 600.0, alignment: Alignment.center, constraints: BoxConstraints(minHeight: 170.0), padding: const EdgeInsets.all(10.0), @@ -214,7 +218,6 @@ class _AccountGeneratePageState extends State { ), const SizedBox(height: 32.0), ButtonText( - width: 450, text: lang.next, enable: _mnemonicChecked, action: () { @@ -245,7 +248,7 @@ class _AccountGeneratePageState extends State { const SizedBox(height: 32.0), Container( height: 50.0, - width: 450.0, + width: 600.0, padding: const EdgeInsets.symmetric(horizontal: 20.0), decoration: BoxDecoration( color: color.surface, @@ -271,7 +274,7 @@ class _AccountGeneratePageState extends State { }), ), const SizedBox(height: 32.0), - ButtonText(width: 450, text: lang.ok, action: () => registerNewAction(lang.setPin), + ButtonText(text: lang.ok, action: () => registerNewAction(lang.setPin), enable: this._registerChecked), _footer(lang.hasAccount, () => Navigator.of(context).pop()), ]) @@ -299,8 +302,8 @@ class _AccountGeneratePageState extends State { right: -1.0, child: InkWell( child: Container( - decoration: const ShapeDecoration( - color: Colors.white, + decoration: ShapeDecoration( + color: color.surface, shape: CircleBorder(), ), child: Icon(Icons.add_circle, diff --git a/lib/pages/account_quick.dart b/lib/pages/account_quick.dart new file mode 100644 index 0000000..9a14fbf --- /dev/null +++ b/lib/pages/account_quick.dart @@ -0,0 +1,234 @@ +import 'dart:convert' show base64; +import 'dart:typed_data' show Uint8List; +import 'dart:ui' show Locale; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'package:esse/l10n/localizations.dart'; +import 'package:esse/utils/mnemonic.dart'; +import 'package:esse/utils/device_info.dart'; +import 'package:esse/widgets/button_text.dart'; +import 'package:esse/widgets/shadow_dialog.dart'; +import 'package:esse/widgets/show_pin.dart'; +import 'package:esse/widgets/select_avatar.dart'; +import 'package:esse/account.dart'; +import 'package:esse/global.dart'; +import 'package:esse/rpc.dart'; +import 'package:esse/provider.dart'; +import 'package:esse/options.dart'; + +import 'package:esse/pages/account_domain.dart'; +import 'package:esse/apps/device/provider.dart'; +import 'package:esse/apps/chat/provider.dart'; +import 'package:esse/apps/group_chat/provider.dart'; + +class AccountQuickPage extends StatefulWidget { + const AccountQuickPage({Key? key}) : super(key: key); + + @override + _AccountQuickPageState createState() => _AccountQuickPageState(); +} + +class _AccountQuickPageState extends State { + bool _registerChecked = false; + + TextEditingController _nameController = new TextEditingController(); + FocusNode _nameFocus = new FocusNode(); + + Uint8List? _imageBytes; + + @override + initState() { + super.initState(); + + _nameFocus.addListener(() { + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme; + final lang = AppLocalizations.of(context); + final locale = context.read().locale; + + double maxHeight = (MediaQuery.of(context).size.height - 400) / 2; + if (maxHeight < 20.0) { + maxHeight = 20.0; + } + + return Scaffold( + body: SafeArea( + child: Container( + padding: const EdgeInsets.all(20.0), + child: SingleChildScrollView( + child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: < + Widget>[ + _header(lang.newAccountTitle, () => Navigator.of(context).pop()), + SizedBox(height: maxHeight), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + newAccountAvatar(color, lang), + const SizedBox(height: 32.0), + Container( + height: 50.0, + width: 600.0, + padding: const EdgeInsets.symmetric(horizontal: 20.0), + decoration: BoxDecoration( + color: color.surface, + border: Border.all( + color: _nameFocus.hasFocus ? color.primary : color.surface), + borderRadius: BorderRadius.circular(10.0), + ), + child: TextField( + style: TextStyle(fontSize: 16.0), + decoration: InputDecoration( + border: InputBorder.none, + hintText: lang.newAccountName, + ), + controller: _nameController, + focusNode: _nameFocus, + onChanged: (value) { + if (value.length > 0) { + _registerChecked = true; + } else { + _registerChecked = false; + } + setState(() {}); + }), + ), + const SizedBox(height: 32.0), + ButtonText(text: lang.ok, action: () => registerNewAction(locale), + enable: this._registerChecked), + _footer(lang.hasAccount, () => Navigator.of(context).pop()), + ]) + ]) + ) + ) + ), + ); + } + + Future _getMnemonic(Locale locale) async { + final lang = MnemonicLangExtension.fromLocale(locale); + return await generateMnemonic(lang: lang); + } + + void registerNewAction(Locale locale) async { + final mnemonic = await _getMnemonic(locale); + final name = _nameController.text; + final avatar = _imageBytes != null ? base64.encode(_imageBytes!) : ""; + final info = await deviceInfo(); + final lock = ''; + + if (!_registerChecked) { + return; + } + + // send to core node service by rpc. + final res = await httpPost(Global.httpRpc, + 'account-create', [name, lock, mnemonic, avatar, info[0], info[1]]); + + if (res.isOk) { + // save this User + final account = Account(res.params[0], name, lock, avatar); + + Provider.of(context, listen: false).addAccount(account); + Provider.of(context, listen: false).updateActived(); + Provider.of(context, listen: false).updateActived(); + Provider.of(context, listen: false).updateActived(); + + Navigator.push(context, MaterialPageRoute(builder: (_) => AccountDomainScreen( + name: name, + ))); + } else { + // TODO tostor error + print(res.error); + } + } + + Widget newAccountAvatar(color, lang) { + return Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: color.surface, + image: _imageBytes != null ? DecorationImage( + image: MemoryImage(_imageBytes!), + fit: BoxFit.cover, + ) : null, + borderRadius: BorderRadius.circular(15.0)), + child: Stack( + alignment: Alignment.center, + children: [ + if (_imageBytes == null) + Icon(Icons.camera_alt, size: 47.0, color: Color(0xFFADB0BB)), + Positioned( + bottom: -1.0, + right: -1.0, + child: InkWell( + child: Container( + decoration: const ShapeDecoration( + color: Colors.white, + shape: CircleBorder(), + ), + child: Icon(Icons.add_circle, + size: 32.0, color: color.primary), + ), + onTap: () => selectAvatar(context, (bytes) => setState(() { + _imageBytes = bytes; + })), + ), + ), + ], + ), + ); + } +} + +Widget _header(String value, VoidCallback callback) { + return Container( + width: 700.0, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + GestureDetector( + onTap: callback, + child: Container( + width: 40.0, + height: 40.0, + decoration: BoxDecoration( + color: Color(0xFF6174FF), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Icon(Icons.arrow_back, color: Colors.white)), + )), + const SizedBox(width: 32.0), + Text( + value, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + ])); +} + +Widget _footer(String text1, VoidCallback callback) { + return Padding( + padding: const EdgeInsets.only(top: 20), + child: Center( + child: TextButton( + onPressed: callback, + child: Text( + text1, + style: TextStyle(fontSize: 16), + ), + ), + ), + ); +} diff --git a/lib/pages/account_restore.dart b/lib/pages/account_restore.dart index 484e9d2..2a54a5c 100644 --- a/lib/pages/account_restore.dart +++ b/lib/pages/account_restore.dart @@ -119,7 +119,7 @@ class _AccountRestorePageState extends State { children: [ Container( height: 50.0, - width: 450.0, + width: 600.0, child: Row(children: [ Expanded( child: Container( @@ -187,7 +187,7 @@ class _AccountRestorePageState extends State { ), const SizedBox(height: 16.0), Container( - width: 450.0, + width: 600.0, alignment: Alignment.center, constraints: BoxConstraints(minHeight: 170.0), padding: const EdgeInsets.all(10.0), @@ -204,7 +204,7 @@ class _AccountRestorePageState extends State { const SizedBox(height: 16.0), Container( height: 50.0, - width: 450.0, + width: 600.0, child: Row(children: [ Container( padding: const EdgeInsets.only(right: 8.0), @@ -275,7 +275,6 @@ class _AccountRestorePageState extends State { ])), const SizedBox(height: 32.0), ButtonText( - width: 450, text: lang.next, enable: _statusChecked, action: () => _mnemonicRegister(lang.unknown, lang.setPin), diff --git a/lib/pages/setting/profile.dart b/lib/pages/setting/profile.dart index b3cce87..cc20d71 100644 --- a/lib/pages/setting/profile.dart +++ b/lib/pages/setting/profile.dart @@ -268,15 +268,19 @@ class _ProfileDetailState extends State { } _pinCheck(String hash, Function callback, String title) { - showShadowDialog( + if (hash.length > 0) { + showShadowDialog( context, Icons.security_rounded, title, PinWords( - hashPin: hash, - callback: (_key, _hash) async { - Navigator.of(context).pop(); - callback(); - })); + hashPin: hash, + callback: (_key, _hash) async { + Navigator.of(context).pop(); + callback(); + })); + } else { + callback(); + } } } diff --git a/lib/security.dart b/lib/security.dart index a70b20c..06a9e0b 100644 --- a/lib/security.dart +++ b/lib/security.dart @@ -9,6 +9,7 @@ import 'package:esse/widgets/shadow_dialog.dart'; import 'package:esse/widgets/show_pin.dart'; import 'package:esse/pages/account_generate.dart'; import 'package:esse/pages/account_restore.dart'; +import 'package:esse/pages/account_quick.dart'; import 'package:esse/utils/logined_cache.dart'; import 'package:esse/utils/better_print.dart'; import 'package:esse/account.dart'; @@ -83,8 +84,8 @@ class _SecurityPageState extends State { children: [ SizedBox(height: maxHeight), Container( - width: 100.0, - height: 100.0, + width: 120.0, + height: 120.0, decoration: BoxDecoration( boxShadow: [ BoxShadow( @@ -103,12 +104,26 @@ class _SecurityPageState extends State { ) ), const SizedBox(height: 40.0), - Text('ESSE', style: TextStyle(fontSize: 20.0)), - const SizedBox(height: 80.0), + Text('ESSE', style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), + const SizedBox(height: 40.0), loginForm(color, lang), const SizedBox(height: 20.0), - ButtonText(width: 450.0, text: lang.ok, enable: _accountsLoaded, + ButtonText(text: lang.ok, enable: _accountsLoaded, action: () => loginAction(lang.verifyPin)), + const SizedBox(height: 20.0), + InkWell( + child: Container(width: 600.0, height: 50.0, + decoration: BoxDecoration( + border: Border.all(color: Color(0xFF6174FF)), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Text(lang.loginQuick, style: TextStyle( + fontSize: 20.0, color: Color(0xFF6174FF) + ))), + ), + onTap: () => Navigator.push(context, + MaterialPageRoute(builder: (_) => AccountQuickPage()) + ), + ), Padding( padding: const EdgeInsets.only(top: 20), child: Container( @@ -211,37 +226,45 @@ class _SecurityPageState extends State { }); } - void loginAction(String title) { - showShadowDialog( - context, - Icons.security_rounded, - title, - PinWords( - hashPin: this._selectedUserLock, - callback: (pinWords, lock) async { - Navigator.of(context).pop(); - final res = await httpPost(Global.httpRpc, 'account-login', - [this._selectedUserId, lock]); + void _verifyAfter(String lock) async { + final res = await httpPost(Global.httpRpc, 'account-login', + [this._selectedUserId, lock]); + + if (res.isOk) { + final mainAccount = this._accounts[this._selectedUserId]!; - if (res.isOk) { - final mainAccount = this._accounts[this._selectedUserId]!; + Provider.of(context, listen: false).updateActivedAccount(mainAccount.gid); + Provider.of(context, listen: false).updateActived(); + Provider.of(context, listen: false).updateActived(); + Provider.of(context, listen: false).updateActived(); - Provider.of(context, listen: false).updateActivedAccount(mainAccount.gid); - Provider.of(context, listen: false).updateActived(); - Provider.of(context, listen: false).updateActived(); - Provider.of(context, listen: false).updateActived(); + Navigator.of(context).pushNamedAndRemoveUntil("/", (Route route) => false); + } else { + // TODO tostor error + print(res.error); + } + } - Navigator.of(context).pushNamedAndRemoveUntil("/", (Route route) => false); - } else { - // TODO tostor error - print(res.error); - } - })); + void loginAction(String title) { + if (this._selectedUserLock.length == 0) { + _verifyAfter(''); + } else { + showShadowDialog( + context, + Icons.security_rounded, + title, + PinWords( + hashPin: this._selectedUserLock, + callback: (pinWords, lock) async { + Navigator.of(context).pop(); + _verifyAfter(lock); + })); + } } Widget loginForm(ColorScheme color, AppLocalizations lang) { return Container( - width: 450.0, + width: 600.0, height: 50.0, padding: EdgeInsets.only(left: 20, right: 20), decoration: BoxDecoration( diff --git a/lib/utils/mnemonic.dart b/lib/utils/mnemonic.dart index 95a2800..b4b65e8 100644 --- a/lib/utils/mnemonic.dart +++ b/lib/utils/mnemonic.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; +import 'dart:ui' show Locale; import 'package:flutter/services.dart' show rootBundle; @@ -66,6 +67,17 @@ extension MnemonicLangExtension on MnemonicLang { return MnemonicLang.NONE; } } + + static MnemonicLang fromLocale(Locale locale) { + switch (locale.languageCode) { + case 'en': + return MnemonicLang.ENGLISH; + case 'zh': + return MnemonicLang.CHINESE_SIMPLIFIED; + default: + return MnemonicLang.ENGLISH; + } + } } final _langCache = Map>();