From 5115c468e5ab787a63399b2bf464fc2b1e6f2fc0 Mon Sep 17 00:00:00 2001 From: Sun Date: Mon, 4 Oct 2021 20:55:19 +0800 Subject: [PATCH] Domain: add search function --- lib/apps/chat/add.dart | 61 +++++++++++++++++------ lib/apps/domain/models.dart | 9 ++++ lib/apps/domain/page.dart | 88 +++++++++++++++++++++++++++++----- lib/l10n/localizations.dart | 1 + lib/l10n/localizations_en.dart | 2 + lib/l10n/localizations_zh.dart | 2 + lib/security.dart | 2 +- src/apps/domain/layer.rs | 28 ++++++----- src/apps/domain/models.rs | 30 +++++++++--- src/apps/domain/rpc.rs | 78 +++++++++++++++++++++++++----- 10 files changed, 241 insertions(+), 60 deletions(-) diff --git a/lib/apps/chat/add.dart b/lib/apps/chat/add.dart index 240d019..7c10e2e 100644 --- a/lib/apps/chat/add.dart +++ b/lib/apps/chat/add.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -44,18 +45,20 @@ class _ChatAddPageState extends State { if (isOk && app == 'add-friend' && params.length == 3) { setState(() { this._showHome = false; + final avatar = Avatar(name: params[2], width: 100.0, colorSurface: false); this._coreScreen = _InfoScreen( callback: this._sendCallback, id: params[0], addr: params[1], name: params[2], - bio: '' + bio: '', + avatar: avatar, ); }); } } - void _searchCallBack(String id, String addr, String name, String bio) { + void _searchCallBack(String id, String addr, String name, String bio, Avatar avatar) { setState(() { this._showHome = false; this._coreScreen = _InfoScreen( @@ -63,7 +66,8 @@ class _ChatAddPageState extends State { id: id, addr: addr, name: name, - bio: bio + bio: bio, + avatar: avatar, ); }); } @@ -134,12 +138,14 @@ class _ChatAddPageState extends State { if (widget.id != '') { setState(() { this._showHome = false; + final avatar = Avatar(name: widget.name, width: 100.0, colorSurface: false); this._coreScreen = _InfoScreen( callback: this._sendCallback, name: widget.name, id: widget.id, addr: widget.addr, bio: '', + avatar: avatar, ); }); } @@ -246,22 +252,37 @@ class _DomainSearchScreenState extends State<_DomainSearchScreen> { params[0].forEach((param) { final provider = ProviderServer.fromList(param); if (provider.isDefault) { - _selectedProvider = provider.id; + this._selectedProvider = provider.id; } this._providers.add(provider); }); + + if (this._selectedProvider == null && this._providers.length > 0) { + this._selectedProvider = this._providers[0].id; + } + setState(() {}); } _searchResult(List params) { - print(params); - if (params.length == 5) { + String name = params[0].trim(); + Avatar avatar = Avatar(name: name, width: 100.0, colorSurface: false); + if (params[4].length > 0) { + avatar = Avatar( + name: name, + avatar: base64.decode(params[4]), + width: 100.0, + colorSurface: false + ); + } + widget.callback( - "EHAAAA...AAAAAAA", - '0xaaaaaaa....aaaaaaaaa', - _nameController.text.trim(), - 'aaa' + params[1], + params[2], + name, + params[3], + avatar, ); } else { setState(() { @@ -309,7 +330,7 @@ class _DomainSearchScreenState extends State<_DomainSearchScreen> { canvasColor: color.surface, ), child: DropdownButton( - hint: Text(lang.loginChooseAccount, style: TextStyle(fontSize: 16)), + hint: Text('-', style: TextStyle(fontSize: 16)), iconEnabledColor: Color(0xFFADB0BB), value: _selectedProvider, onChanged: (int? m) { @@ -345,9 +366,17 @@ class _DomainSearchScreenState extends State<_DomainSearchScreen> { action: () => setState(() { final name = this._nameController.text.trim(); if (name.length > 0) { - rpc.send('domain-search', [name]); - this._waiting = true; - this._searchNone = false; + String addr = ''; + this._providers.forEach((v) { + if (v.id == this._selectedProvider) { + addr = v.addr; + } + }); + if (addr.length > 0) { + rpc.send('domain-search', [addr, name]); + this._waiting = true; + this._searchNone = false; + } } }), text: this._waiting ? lang.waiting : lang.search, width: 600.0), ] @@ -439,6 +468,7 @@ class _InfoScreen extends StatelessWidget { final String addr; final String name; final String bio; + final Avatar avatar; const _InfoScreen({ Key? key, @@ -447,6 +477,7 @@ class _InfoScreen extends StatelessWidget { required this.addr, required this.name, required this.bio, + required this.avatar, }) : super(key: key); @override @@ -463,7 +494,7 @@ class _InfoScreen extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Avatar(name: this.name, width: 100.0, colorSurface: false), + avatar, Padding( padding: const EdgeInsets.symmetric(vertical: 10.0), child: Text(this.name, style: TextStyle(fontWeight: FontWeight.bold)), diff --git a/lib/apps/domain/models.dart b/lib/apps/domain/models.dart index bd1395c..d736b7f 100644 --- a/lib/apps/domain/models.dart +++ b/lib/apps/domain/models.dart @@ -7,6 +7,15 @@ class ProviderServer { bool isProxy; bool isActived; + ProviderServer.empty(): + this.id = 0, + this.name = '', + this.addr = '', + this.isOk = false, + this.isDefault = false, + this.isProxy = false, + this.isActived = false; + ProviderServer.fromList(List params): this.id = params[0], this.name = params[1], diff --git a/lib/apps/domain/page.dart b/lib/apps/domain/page.dart index 6886392..9b2ba4b 100644 --- a/lib/apps/domain/page.dart +++ b/lib/apps/domain/page.dart @@ -46,10 +46,20 @@ class _DomainDetailState extends State { _domainProviderAdd(List params) { setState(() { + this._listHome = true; + this._showProviders = true; this._providers[params[0]] = ProviderServer.fromList(params); }); } + _domainRegisterSuccess(List params) { + setState(() { + this._listHome = true; + this._showProviders = false; + this._names.add(Name.fromList(params)); + }); + } + @override void initState() { super.initState(); @@ -57,6 +67,7 @@ class _DomainDetailState extends State { // resigter rpc for current page. rpc.addListener('domain-list', _domainList, false); rpc.addListener('domain-provider-add', _domainProviderAdd, false); + rpc.addListener('domain-register-success', _domainRegisterSuccess, false); rpc.send('domain-list', []); } @@ -68,7 +79,7 @@ class _DomainDetailState extends State { return Scaffold( appBar: AppBar( - title: Text(lang.domain + ' (${lang.wip})'), + title: Text(lang.domain), bottom: PreferredSize( child: Container(color: const Color(0x40ADB0BB), height: 1.0), preferredSize: Size.fromHeight(1.0) @@ -95,7 +106,9 @@ class _DomainDetailState extends State { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: this._listHome ? (this._showProviders ? _ListProviderScreen(this._providers) : _ListNameScreen(this._providers, this._names)) - : (this._showProviders ? _AddProviderScreen() : _RegisterScreen()), + : ((!this._showProviders && this._providers.length > 0) + ? _RegisterScreen(this._providers.values.toList().asMap()) : _AddProviderScreen() + ), ))), floatingActionButton: FloatingActionButton( onPressed: () => setState(() { @@ -207,7 +220,8 @@ class _ListProviderScreen extends StatelessWidget { } class _RegisterScreen extends StatefulWidget { - const _RegisterScreen({Key? key}) : super(key: key); + final Map providers; + const _RegisterScreen(this.providers); @override _RegisterScreenState createState() => _RegisterScreenState(); @@ -215,8 +229,9 @@ class _RegisterScreen extends StatefulWidget { class _RegisterScreenState extends State<_RegisterScreen> { bool _showProviders = false; - List _providers = ['']; - int _providerSelected = 0; + int _providerSelected = -1; + bool _registerNone = false; + bool _waiting = false; TextEditingController _nameController = TextEditingController(); TextEditingController _bioController = TextEditingController(); @@ -224,13 +239,35 @@ class _RegisterScreenState extends State<_RegisterScreen> { FocusNode _nameFocus = FocusNode(); FocusNode _bioFocus = FocusNode(); + _domainRegisterFailure(List _params) { + setState(() { + this._waiting = false; + this._registerNone = true; + }); + } + + @override + void initState() { + super.initState(); + rpc.addListener('domain-register-failure', _domainRegisterFailure, false); + } + + @override Widget build(BuildContext context) { final color = Theme.of(context).colorScheme; final lang = AppLocalizations.of(context); - this._providers = ["domain.esse", "eth.esse", "btc.esse"]; - final maxIndex = this._providers.length - 1; + if (this._providerSelected < 0) { + this._providerSelected = 0; + widget.providers.forEach((k, v) { + if (v.isDefault) { + this._providerSelected = k; + } + }); + } + + final maxIndex = widget.providers.length - 1; return Column( children: [ @@ -249,7 +286,9 @@ class _RegisterScreenState extends State<_RegisterScreen> { Expanded( child: Center( child: Text( - this._providers[this._providerSelected], + widget.providers.length >= this._providerSelected + ? widget.providers[this._providerSelected]!.name + : '', style: TextStyle(fontWeight: FontWeight.bold)), )), TextButton(child: Icon(Icons.navigate_next), @@ -275,15 +314,32 @@ class _RegisterScreenState extends State<_RegisterScreen> { controller: _bioController, focus: _bioFocus), ), - const SizedBox(height: 20.0), - ButtonText(action: () {}, text: lang.send), + SizedBox( + height: 40.0, + child: Center(child: Text(this._registerNone ? lang.domainRegisterFailure : '', + style: TextStyle(color: Colors.red))), + ), + ButtonText( + enable: !this._waiting, + action: () { + String name = _nameController.text.trim(); + String bio = _bioController.text.trim(); + if (name.length > 0 && widget.providers.length >= this._providerSelected) { + final provider = widget.providers[this._providerSelected]!.id; + final addr = widget.providers[this._providerSelected]!.addr; + rpc.send('domain-register', [provider, addr, name, bio]); + setState(() { + this._waiting = true; + }); + } + }, text: this._waiting ? lang.waiting : lang.send), ] ); } } class _AddProviderScreen extends StatefulWidget { - const _AddProviderScreen({Key? key}) : super(key: key); + const _AddProviderScreen(); @override _AddProviderScreenState createState() => _AddProviderScreenState(); @@ -292,6 +348,7 @@ class _AddProviderScreen extends StatefulWidget { class _AddProviderScreenState extends State<_AddProviderScreen> { TextEditingController _addrController = TextEditingController(); FocusNode _addrFocus = FocusNode(); + bool _waiting = false; @override Widget build(BuildContext context) { @@ -312,13 +369,18 @@ class _AddProviderScreenState extends State<_AddProviderScreen> { controller: _addrController, focus: _addrFocus), ), - ButtonText(action: () { + ButtonText( + enable: !this._waiting, + action: () { String addr = _addrController.text.trim(); if (addr.substring(0, 2) == '0x') { addr = addr.substring(2); } rpc.send('domain-provider-add', [addr]); - }, text: lang.send), + setState(() { + this._waiting = true; + }); + }, text: this._waiting ? lang.waiting : lang.send), ] ); } diff --git a/lib/l10n/localizations.dart b/lib/l10n/localizations.dart index e8328f9..51df637 100644 --- a/lib/l10n/localizations.dart +++ b/lib/l10n/localizations.dart @@ -215,6 +215,7 @@ abstract class AppLocalizations { String get domainProviderAdress; String get domainAddProvider; String get domainSearch; + String get domainRegisterFailure; } class _AppLocalizationsDelegate diff --git a/lib/l10n/localizations_en.dart b/lib/l10n/localizations_en.dart index f740b82..ac4aa18 100644 --- a/lib/l10n/localizations_en.dart +++ b/lib/l10n/localizations_en.dart @@ -334,4 +334,6 @@ class AppLocalizationsEn extends AppLocalizations { String get domainAddProvider => 'Add new provider'; @override String get domainSearch => 'Domain Search'; + @override + String get domainRegisterFailure => 'name already existed!'; } diff --git a/lib/l10n/localizations_zh.dart b/lib/l10n/localizations_zh.dart index fb59caf..c8c87fe 100644 --- a/lib/l10n/localizations_zh.dart +++ b/lib/l10n/localizations_zh.dart @@ -334,4 +334,6 @@ class AppLocalizationsZh extends AppLocalizations { String get domainAddProvider => '添加新的服务商'; @override String get domainSearch => '域名搜索'; + @override + String get domainRegisterFailure => '用户名已存在!'; } diff --git a/lib/security.dart b/lib/security.dart index faac3be..4702066 100644 --- a/lib/security.dart +++ b/lib/security.dart @@ -103,7 +103,7 @@ class _SecurityPageState extends State { const SizedBox(height: 80.0), loginForm(color, lang), const SizedBox(height: 20.0), - ButtonText(text: lang.ok, enable: _accountsLoaded, + ButtonText(width: 450.0, text: lang.ok, enable: _accountsLoaded, action: () => loginAction(lang.verifyPin)), Padding( padding: const EdgeInsets.only(top: 20), diff --git a/src/apps/domain/layer.rs b/src/apps/domain/layer.rs index bb94f60..0088067 100644 --- a/src/apps/domain/layer.rs +++ b/src/apps/domain/layer.rs @@ -22,7 +22,7 @@ pub(crate) async fn handle( ogid: GroupId, msg: RecvType, ) -> Result { - let results = HandleResult::new(); + let mut results = HandleResult::new(); match msg { RecvType::Connect(..) @@ -34,7 +34,7 @@ pub(crate) async fn handle( } RecvType::Event(addr, bytes) => { // server & client handle it. - let LayerServerEvent(event, proof) = bincode::deserialize(&bytes)?; + let LayerServerEvent(event, _proof) = bincode::deserialize(&bytes)?; let db = domain_db(layer.read().await.base(), &ogid)?; @@ -46,24 +46,28 @@ pub(crate) async fn handle( ); let mut provider = Provider::get_by_addr(&db, &addr)?; - provider.ok(&db, name, support_request); - - // TODO UI: add new provider. + provider.ok(&db, name, support_request)?; + results.rpcs.push(rpc::add_provider(ogid, &provider)); } ServerEvent::Result(name, is_ok) => { let provider = Provider::get_by_addr(&db, &addr)?; - let mut name = Name::get_by_name_provider(&db, &name, &provider.id)?; + let mut user = Name::get_by_name_provider(&db, &name, &provider.id)?; if is_ok { - //name.ok(&db); + Name::active(&db, &user.id, true)?; + user.is_ok = true; + user.is_actived = true; + results.rpcs.push(rpc::register_success(ogid, &user)); } else { - //name.delete(&db); + Name::delete(&db, &user.id)?; + results.rpcs.push(rpc::register_failure(ogid, &name)); } - - // TODO UI: add new regsiter name. } - ServerEvent::Info(_uname, _ugid, _uaddr, _ubio, _uavatar) => { - // TODO UI: show search result. + ServerEvent::Info(uname, ugid, uaddr, ubio, uavatar) => { + println!("------ Search: {} --", uname); + results.rpcs.push(rpc::search_result( + ogid, &uname, &ugid, &uaddr, &ubio, &uavatar, + )); } ServerEvent::None(_name) => { // TODO UI: show search result. diff --git a/src/apps/domain/models.rs b/src/apps/domain/models.rs index 6ae3d82..ec39bc9 100644 --- a/src/apps/domain/models.rs +++ b/src/apps/domain/models.rs @@ -159,20 +159,31 @@ impl Provider { /// Name Model. pub(crate) struct Name { /// db auto-increment id. - id: i64, + pub id: i64, /// provider database id. provider: i64, /// name. - name: String, + pub name: String, /// bio. - bio: String, + pub bio: String, /// is add ok. - is_ok: bool, + pub is_ok: bool, /// is actived. - is_actived: bool, + pub is_actived: bool, } impl Name { + pub fn prepare(name: String, bio: String, provider: i64) -> Self { + Self { + name, + bio, + provider, + is_ok: false, + is_actived: false, + id: 0, + } + } + pub fn to_rpc(&self) -> RpcParam { json!([ self.id, @@ -197,7 +208,9 @@ impl Name { /// use in rpc when load providers. pub fn list(db: &DStorage) -> Result> { - let matrix = db.query("SELECT id, provider, name, bio, is_ok, is_actived FROM names")?; + let matrix = db.query( + "SELECT id, provider, name, bio, is_ok, is_actived FROM names WHERE is_ok = true", + )?; let mut names = vec![]; for values in matrix { names.push(Self::from_values(values)); @@ -270,7 +283,10 @@ impl Name { /// active/suspend the name. pub fn active(db: &DStorage, id: &i64, active: bool) -> Result<()> { - let sql = format!("UPDATE names SET is_actived = {} WHERE id = {}", active, id); + let sql = format!( + "UPDATE names SET is_ok = true, is_actived = {} WHERE id = {}", + active, id + ); db.update(&sql)?; Ok(()) } diff --git a/src/apps/domain/rpc.rs b/src/apps/domain/rpc.rs index c24cbc5..be8b243 100644 --- a/src/apps/domain/rpc.rs +++ b/src/apps/domain/rpc.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use tdn::types::{ group::GroupId, primitive::{HandleResult, PeerAddr}, - rpc::{json, RpcError, RpcHandler, RpcParam}, + rpc::{json, rpc_response, RpcError, RpcHandler, RpcParam}, }; use domain_types::PeerEvent; @@ -13,6 +13,48 @@ use super::{ }; use crate::{rpc::RpcState, storage::domain_db}; +#[inline] +pub(crate) fn add_provider(mgid: GroupId, provider: &Provider) -> RpcParam { + rpc_response(0, "domain-provider-add", json!(provider.to_rpc()), mgid) +} + +#[inline] +pub(crate) fn register_success(mgid: GroupId, name: &Name) -> RpcParam { + rpc_response(0, "domain-register-success", json!(name.to_rpc()), mgid) +} + +#[inline] +pub(crate) fn register_failure(mgid: GroupId, name: &str) -> RpcParam { + rpc_response(0, "domain-register-failure", json!([name]), mgid) +} + +#[inline] +pub(crate) fn search_result( + mgid: GroupId, + name: &str, + gid: &GroupId, + addr: &PeerAddr, + bio: &str, + avatar: &Vec, +) -> RpcParam { + rpc_response( + 0, + "domain-search", + json!([ + name, + gid.to_hex(), + addr.to_hex(), + bio, + if avatar.len() > 0 { + base64::encode(avatar) + } else { + "".to_owned() + } + ]), + mgid, + ) +} + pub(crate) fn new_rpc_handler(handler: &mut RpcHandler) { handler.add_method( "domain-list", @@ -66,17 +108,23 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler) { handler.add_method( "domain-register", |gid: GroupId, params: Vec, state: Arc| async move { - let _provider = params[0].as_i64().ok_or(RpcError::ParseError)?; - let _symbol = params[1].as_str().ok_or(RpcError::ParseError)?.to_string(); - let _bio = params[2].as_str().ok_or(RpcError::ParseError)?.to_string(); - - let _me = state.group.read().await.clone_user(&gid)?; + let provider = params[0].as_i64().ok_or(RpcError::ParseError)?; + let addr = PeerAddr::from_hex(params[1].as_str().ok_or(RpcError::ParseError)?)?; + let name = params[2].as_str().ok_or(RpcError::ParseError)?.to_string(); + let bio = params[3].as_str().ok_or(RpcError::ParseError)?.to_string(); - // Send to remote domain service. + let me = state.group.read().await.clone_user(&gid)?; - // + // save to db. + let mut results = HandleResult::new(); + let db = domain_db(state.layer.read().await.base(), &gid)?; + let mut u = Name::prepare(name, bio, provider); + u.insert(&db)?; - Ok(HandleResult::rpc(json!(params))) + // send to server. + let event = PeerEvent::Register(u.name, u.bio, me.avatar); + add_layer(&mut results, addr, event, gid)?; + Ok(results) }, ); @@ -100,10 +148,16 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler) { handler.add_method( "domain-search", - |_gid: GroupId, params: Vec, _state: Arc| async move { - let _name = params[0].as_str().ok_or(RpcError::ParseError)?; + |gid: GroupId, params: Vec, _state: Arc| async move { + let addr = PeerAddr::from_hex(params[0].as_str().ok_or(RpcError::ParseError)?)?; + let name = params[1].as_str().ok_or(RpcError::ParseError)?.to_owned(); - Ok(HandleResult::rpc(json!(params))) + let mut results = HandleResult::new(); + + // send to server. + let event = PeerEvent::Search(name); + add_layer(&mut results, addr, event, gid)?; + Ok(results) }, ); }