From 5541d9d7d721e825ed492828a6a495d0c72679fd Mon Sep 17 00:00:00 2001 From: Sun Date: Tue, 1 Jun 2021 16:48:35 +0800 Subject: [PATCH] contact & group card UI --- lib/apps/chat/detail.dart | 1 + lib/apps/group_chat/detail.dart | 3 +- lib/apps/primitives.dart | 13 +- lib/l10n/localizations_en.dart | 2 +- lib/session.dart | 1 - lib/widgets/chat_message.dart | 270 ++++++++++---------------------- lib/widgets/user_info.dart | 48 +++++- src/apps/group_chat/models.rs | 14 +- src/apps/group_chat/rpc.rs | 48 ++++-- 9 files changed, 194 insertions(+), 206 deletions(-) diff --git a/lib/apps/chat/detail.dart b/lib/apps/chat/detail.dart index c7bad58..f1287f4 100644 --- a/lib/apps/chat/detail.dart +++ b/lib/apps/chat/detail.dart @@ -366,6 +366,7 @@ class _ChatDetailState extends State { itemCount: recentMessageKeys.length, reverse: true, itemBuilder: (BuildContext context, index) => ChatMessage( + fgid: friend.gid, name: friend.name, message: recentMessages[recentMessageKeys[index]], ) diff --git a/lib/apps/group_chat/detail.dart b/lib/apps/group_chat/detail.dart index 803dcad..b7b8db5 100644 --- a/lib/apps/group_chat/detail.dart +++ b/lib/apps/group_chat/detail.dart @@ -295,7 +295,7 @@ class _GroupChatDetailState extends State { id: 'EG' + this.group.gid.toUpperCase(), name: this.group.name, addr: '0x' + this.group.addr, - title: this.group.type.lang(lang) + ' ' + lang.groupChat, + title: this.group.type.lang(lang), bio: this.group.bio, ), 0.0, @@ -371,6 +371,7 @@ class _GroupChatDetailState extends State { final message = recentMessages[recentMessageKeys[index]]; final member = members[message.fid]; return ChatMessage( + fgid: member.mid, avatar: member.showAvatar(), name: member.name, message: message, diff --git a/lib/apps/primitives.dart b/lib/apps/primitives.dart index 5696da0..157e343 100644 --- a/lib/apps/primitives.dart +++ b/lib/apps/primitives.dart @@ -101,6 +101,7 @@ class BaseMessage { var addr = ''; var name = ''; var proof = ''; + var key = ''; final i_type = this.content.indexOf(';;'); if (i_type > 0) { @@ -124,9 +125,17 @@ class BaseMessage { if (i_name > 0) { name = raw_2.substring(0, i_name).replaceAll('-;', ';'); } - proof = raw_2.substring(i_name + 2); - return [type, gid, addr, name, proof]; + final raw_3 = raw_2.substring(i_name + 2); + final i_proof = raw_3.indexOf(';;'); + if (i_proof > 0) { + proof = raw_3.substring(0, i_proof); + key = raw_3.substring(i_proof + 2); + } else { + proof = raw_3; + } + + return [type, gid, addr, name, proof, key]; } static String rawRecordName(int time, String name) { diff --git a/lib/l10n/localizations_en.dart b/lib/l10n/localizations_en.dart index e46c3c5..e3c5107 100644 --- a/lib/l10n/localizations_en.dart +++ b/lib/l10n/localizations_en.dart @@ -249,7 +249,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get assistantBio => 'Jarvis is a robot, only belongs to you.'; @override - String get groupChat => 'Group Chats'; + String get groupChat => 'Group Chat'; @override String get groupChatAdd => 'Add Group Chat'; @override diff --git a/lib/session.dart b/lib/session.dart index 83937ee..c119e84 100644 --- a/lib/session.dart +++ b/lib/session.dart @@ -127,7 +127,6 @@ class Session { avatarPath: avatar, online: this.online != OnlineType.Lost, onlineColor: color, - hasNew: !this.lastReaded, loading: this.online == OnlineType.Waiting, ); } diff --git a/lib/widgets/chat_message.dart b/lib/widgets/chat_message.dart index 9bc41e4..11844d4 100644 --- a/lib/widgets/chat_message.dart +++ b/lib/widgets/chat_message.dart @@ -13,24 +13,50 @@ import 'package:esse/utils/better_print.dart'; import 'package:esse/widgets/avatar.dart'; import 'package:esse/widgets/audio_player.dart'; import 'package:esse/widgets/shadow_dialog.dart'; +import 'package:esse/widgets/user_info.dart'; import 'package:esse/global.dart'; import 'package:esse/apps/primitives.dart'; import 'package:esse/apps/chat/models.dart' show Request; +import 'package:esse/apps/group_chat/models.dart' show GroupType, GroupTypeExtension; import 'package:esse/apps/chat/provider.dart'; +import 'package:esse/apps/group_chat/provider.dart'; class ChatMessage extends StatelessWidget { final Widget avatar; + final String fgid; final String name; final BaseMessage message; - const ChatMessage({Key key, this.name, this.message, this.avatar}): super(key: key); + const ChatMessage({Key key, this.fgid, this.name, this.message, this.avatar}): super(key: key); - Widget _showText(context, color, isDesktop) { - final width = MediaQuery.of(context).size.width * 0.6; + Widget _showContactCard(Widget avatar, String gid, String name, String title, ColorScheme color) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), + width: 200.0, + decoration: BoxDecoration(color: const Color(0x40ADB0BB), borderRadius: BorderRadius.circular(15.0)), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + avatar, + Container(width: 135.0, padding: const EdgeInsets.only(left: 10.0), + child: Column(children: [ + Text(name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: color.onPrimary, fontSize: 16.0)), + const SizedBox(height: 4.0), + Text(betterPrint(gid), style: TextStyle(color: Colors.grey, fontSize: 12.0)), + ]))]), + const SizedBox(height: 5.0), + const Divider(height: 1.0, color: Color(0x40ADB0BB)), + const SizedBox(height: 3.0), + Text(title, style: TextStyle(color: Colors.grey, fontSize: 10.0)), + ]) + ); + } + + Widget _showText(context, color, maxWidth) { // text return Container( - constraints: BoxConstraints(minWidth: 50, maxWidth: isDesktop ? width - 300.0 : width), + constraints: BoxConstraints(minWidth: 50, maxWidth: maxWidth), padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 14.0), decoration: BoxDecoration( color: message.isMe ? Color(0xFF6174FF) : color.primaryVariant, @@ -182,199 +208,73 @@ class ChatMessage extends StatelessWidget { ); } - Widget _showContact(context, lang, color) { + Widget _showContact(context, lang, color, maxWidth) { // contact [name, gid, addr, avatar] final infos = message.showContact(); - final gid = 'EH' + infos[1].toUpperCase(); - - if (infos != null) { + if (infos[1].length > 0) { + final gid = 'EH' + infos[1].toUpperCase(); return GestureDetector( onTap: () => showShadowDialog( context, Icons.person_rounded, - lang.contact, - Column(children: [ - Avatar(width: 100.0, name: infos[0], avatarPath: infos[3]), - const SizedBox(height: 10.0), - Text(infos[0]), - const SizedBox(height: 10.0), - const Divider(height: 1.0, color: Color(0x40ADB0BB)), - const SizedBox(height: 10.0), - _infoListTooltip(Icons.person, color.primary, gid), - _infoListTooltip(Icons.location_on, color.primary, "0x" + infos[2]), - Container( - width: 300.0, - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: Row( - children: [ - Icon(Icons.turned_in, size: 20.0, color: color.primary), - const SizedBox(width: 20.0), - Expanded(child: Text(lang.fromContactCard(name))), - ] - ), - ), - const SizedBox(height: 20.0), - InkWell( - onTap: () { - Navigator.pop(context); - Provider.of(context, listen: false).requestCreate( - Request(infos[1], infos[2], infos[0], lang.fromContactCard(name)) - ); - }, - hoverColor: Colors.transparent, - child: Container( - width: 200.0, - padding: const EdgeInsets.symmetric(vertical: 10.0), - decoration: BoxDecoration( - border: Border.all(color: color.primary), - borderRadius: BorderRadius.circular(10.0)), - child: Center(child: Text(lang.addFriend, - style: TextStyle(fontSize: 14.0, color: color.primary))), - ) - ), - ] - ) - ), - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 10.0), - width: 200.0, - decoration: BoxDecoration( - color: const Color(0x40ADB0BB), - borderRadius: BorderRadius.circular(15.0), + lang.contactCard, + UserInfo( + showQr: false, id: gid, addr: '0x' + infos[2], name: infos[0], + remark: lang.fromContactCard(name), + avatar: Avatar(width: 100.0, name: infos[0], avatarPath: infos[3]), + callback: () { + Navigator.pop(context); + Provider.of(context, listen: false).requestCreate( + Request(infos[1], infos[2], infos[0], lang.fromContactCard(name)) + ); + }, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row(children: [ - Avatar(width: 40.0, name: infos[0], avatarPath: infos[3]), - Container( - width: 135.0, - padding: const EdgeInsets.only(left: 10.0), - child: Column(children: [ - Text(infos[0], - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: color.onPrimary, fontSize: 16.0)), - SizedBox(height: 5.0), - Text(betterPrint(gid), - style: TextStyle( - color: Colors.grey, fontSize: 12.0)), - ])), - ]), - SizedBox(height: 5.0), - const Divider(height: 1.0, color: Color(0x40ADB0BB)), - SizedBox(height: 3.0), - Text(lang.contactCard, - style: TextStyle(color: Colors.grey, fontSize: 10.0)), - ]))); - } else { - return Container( - padding: - const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), - width: 200.0, - decoration: BoxDecoration( - color: const Color(0x40ADB0BB), - borderRadius: BorderRadius.circular(15.0), ), - child: - Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(children: [ - Container( - height: 35.0, - child: Image( - image: AssetImage('assets/images/image_missing.png'), - fit: BoxFit.cover), - ), - Container( - width: 130.0, - padding: const EdgeInsets.only(left: 10.0), - child: Text(message.content, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: color.onPrimary.withOpacity(0.5), - decoration: TextDecoration.lineThrough, - fontSize: 16.0)), - ), - ]), - SizedBox(height: 5.0), - const Divider(height: 1.0, color: Color(0x40ADB0BB)), - SizedBox(height: 3.0), - Text(lang.contactCard, - style: TextStyle(color: Colors.grey, fontSize: 10.0)), - ])); + child: _showContactCard( + Avatar(width: 40.0, name: infos[0], avatarPath: infos[3]), + gid, infos[0], lang.contactCard, color + ) + ); + } else { + return _showText(context, color, maxWidth); } } - Widget _showInvite(context, lang, color) { - // contact [type, gid, addr, name, proof] + Widget _showInvite(context, lang, color, maxWidth) { + // contact [type, gid, addr, name, proof, key] final infos = message.showInvite(); - print(infos); - final gid = 'EG' + infos[1].toUpperCase(); - - if (infos != null) { + if (infos[1].length > 0) { + final gid = 'EG' + infos[1].toUpperCase(); + final GroupType gtype = infos[0]; return GestureDetector( onTap: () => showShadowDialog( context, Icons.groups_rounded, lang.groupChat, - Text(infos[3]), - ), - child: Container( - padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), - width: 200.0, - decoration: BoxDecoration( - color: const Color(0x40ADB0BB), - borderRadius: BorderRadius.circular(15.0), + UserInfo(showQr: false, id: gid, addr: '0x' + infos[2], name: infos[3], + title: gtype.lang(lang), + avatar: Container(width: 100.0, height: 100.0, + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration(color: color.surface, borderRadius: BorderRadius.circular(15.0)), + child: Icon(Icons.groups_rounded, color: color.primary, size: 36.0), + ), + callback: () { + Navigator.pop(context); + Provider.of(context, listen: false).join( + gtype, infos[1], infos[2], infos[3], fgid, infos[4], infos[5] + ); + }, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 40.0, - height: 40.0, - padding: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: color.surface, - borderRadius: BorderRadius.circular(15.0), - ), - child: Icon(Icons.groups_rounded, color: color.primary), - ), - Container( - width: 135.0, - padding: const EdgeInsets.only(left: 10.0), - child: Column(children: [ - Text(infos[3], maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle(color: color.onPrimary, fontSize: 16.0)), - const SizedBox(height: 5.0), - Text(betterPrint(gid), style: TextStyle(color: Colors.grey, fontSize: 12.0)), - ])), - ]), - const SizedBox(height: 5.0), - const Divider(height: 1.0, color: Color(0x40ADB0BB)), - const SizedBox(height: 3.0), - Text(lang.groupChat, style: TextStyle(color: Colors.grey, fontSize: 10.0)), - ]))); - } else { - final width = MediaQuery.of(context).size.width * 0.6; - - // text - return Container( - constraints: BoxConstraints(minWidth: 50, maxWidth: width), - padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 14.0), - decoration: BoxDecoration( - color: message.isMe ? Color(0xFF6174FF) : color.primaryVariant, - borderRadius: BorderRadius.circular(15.0), ), - child: Text(message.content, - style: TextStyle( - color: message.isMe ? Colors.white : Color(0xFF1C1939), - fontSize: 14.0)) + child: _showContactCard( + Container(width: 40.0, height: 40.0, + decoration: BoxDecoration(color: color.surface, borderRadius: BorderRadius.circular(15.0)), + child: Icon(Icons.groups_rounded, color: color.primary, size: 20.0), + ), + gid, infos[3], lang.groupChat, color) ); + } else { + return _showText(context, color, maxWidth); } } @@ -397,21 +297,23 @@ class ChatMessage extends StatelessWidget { ); } - Widget _show(context, color, lang, isDesktop) { + Widget _show(context, color, lang, isDesktop, maxWidth) { + final width = MediaQuery.of(context).size.width * 0.6; + if (message.type == MessageType.String) { - return _showText(context, color, isDesktop); + return _showText(context, color, maxWidth); } else if (message.type == MessageType.Image) { return _showImage(context, lang, color); } else if (message.type == MessageType.File) { return _showFile(context, lang, color); } else if (message.type == MessageType.Contact) { - return _showContact(context, lang, color); + return _showContact(context, lang, color, maxWidth); } else if (message.type == MessageType.Record) { return _showRecord(); } else if (message.type == MessageType.Invite) { - return _showInvite(context, lang, color); + return _showInvite(context, lang, color, maxWidth); } - return _showText(context, color, isDesktop); + return _showText(context, color, maxWidth); } @override @@ -419,7 +321,9 @@ class ChatMessage extends StatelessWidget { final color = Theme.of(context).colorScheme; final lang = AppLocalizations.of(context); final isDesktop = isDisplayDesktop(context); - final messageShow = _show(context, color, lang, isDesktop); + final width = MediaQuery.of(context).size.width * 0.6; + final maxWidth = isDesktop ? width - 300.0 : width; + final messageShow = _show(context, color, lang, isDesktop, maxWidth); final isAvatar = avatar != null && !message.isMe; final timeWidget = Container( diff --git a/lib/widgets/user_info.dart b/lib/widgets/user_info.dart index d9555ac..78d0670 100644 --- a/lib/widgets/user_info.dart +++ b/lib/widgets/user_info.dart @@ -15,13 +15,29 @@ class UserInfo extends StatefulWidget { final String title; final String remark; final String bio; + final Function callback; + final bool showQr; + final Widget avatar; Map qrInfo; - UserInfo({Key key, this.id, this.name, this.addr, this.app, this.title, this.remark, this.bio}) : super(key: key) { - this.qrInfo = { - "app": this.app, - "params": [this.id, this.addr, this.name], - }; + UserInfo({Key key, + this.app, + this.id, + this.addr, + this.name, + this.title, + this.remark, + this.bio, + this.callback, + this.avatar, + this.showQr = true + }) : super(key: key) { + if (this.showQr) { + this.qrInfo = { + "app": this.app, + "params": [this.id, this.addr, this.name], + }; + } } @override @@ -45,10 +61,15 @@ class _UserInfoState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ + if (widget.avatar != null) + widget.avatar, + const SizedBox(height: 10.0), Text(widget.name, style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), const SizedBox(height: 10), + if (widget.showQr) Container( width: 200.0, + margin: const EdgeInsets.only(bottom: 8.0), padding: const EdgeInsets.all(2.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5.0), @@ -84,7 +105,7 @@ class _UserInfoState extends State { ] ) ), - const SizedBox(height: 8), + if (widget.title != null) Text(widget.title, style: TextStyle(fontSize: 16.0, fontStyle: FontStyle.italic)), const SizedBox(height: 10), const Divider(height: 1.0, color: Color(0x40ADB0BB)), @@ -161,6 +182,21 @@ class _UserInfoState extends State { ), ), const SizedBox(height: 16), + if (widget.callback != null) + Container( + width: 250.0, + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: InkWell( + onTap: widget.callback, + hoverColor: Colors.transparent, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10.0), + decoration: BoxDecoration(border: Border.all(color: color.primary), + borderRadius: BorderRadius.circular(10.0)), + child: Center(child: Text(lang.add, style: TextStyle(fontSize: 14.0, color: color.primary))), + ) + ), + ), ] ); } diff --git a/src/apps/group_chat/models.rs b/src/apps/group_chat/models.rs index 3cf8b4f..ba69b19 100644 --- a/src/apps/group_chat/models.rs +++ b/src/apps/group_chat/models.rs @@ -79,7 +79,7 @@ pub(crate) struct GroupChat { /// group chat need manager agree. is_need_agree: bool, /// group chat encrypted-key. - key: GroupChatKey, + pub key: GroupChatKey, /// group chat created time. pub datetime: i64, /// is deleted. @@ -481,6 +481,18 @@ impl Request { Ok(()) } + pub fn exist(db: &DStorage, gcd: &GroupId) -> Result { + let matrix = db.query(&format!( + "SELECT id from requests WHERE gid = '{}' AND is_over = 0", + gcd.to_hex(), + ))?; + if matrix.len() == 0 { + Ok(false) + } else { + Ok(true) + } + } + pub fn over_rid(db: &DStorage, gcd: &GroupId, rid: &i64, is_ok: bool) -> Result { let mut matrix = db.query(&format!( "SELECT id from requests WHERE gid = '{}' AND rid = {} AND is_over = 0", diff --git a/src/apps/group_chat/rpc.rs b/src/apps/group_chat/rpc.rs index 2cc7d08..f0411ee 100644 --- a/src/apps/group_chat/rpc.rs +++ b/src/apps/group_chat/rpc.rs @@ -236,27 +236,52 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler) { handler.add_method( "group-chat-join", |gid: GroupId, params: Vec, state: Arc| async move { - let _gtype = GroupType::from_u32(params[0].as_i64()? as u32); + let gtype = GroupType::from_u32(params[0].as_i64()? as u32); let gcd = GroupId::from_hex(params[1].as_str()?)?; let gaddr = PeerAddr::from_hex(params[2].as_str()?)?; let gname = params[3].as_str()?.to_owned(); - let gremark = params[4].as_str()?.to_owned(); + let gremark = params[4].as_str()?; let gproof = params[5].as_str()?; - let _proof = Proof::from_hex(gproof).unwrap_or(Proof::default()); + let proof = Proof::from_hex(gproof).unwrap_or(Proof::default()); let gkey = params[6].as_str()?; let key = GroupChatKey::from_hex(gkey).unwrap_or(GroupChatKey::new(vec![])); - let mut request = Request::new_by_me(gcd, gaddr, gname, gremark, key); let db = group_chat_db(state.layer.read().await.base(), &gid)?; - request.insert(&db)?; + if GroupChat::get(&db, &gcd)?.is_some() { + debug!("Had joined this group."); + return Ok(HandleResult::new()); // had join this group. + } + + let mut results = HandleResult::new(); + // check request is exsit. + if !Request::exist(&db, &gcd)? { + let mut request = Request::new_by_me(gcd, gaddr, gname, gremark.to_owned(), key); + request.insert(&db)?; + results.rpcs.push(request.to_rpc()); + } else { + debug!("Had request again."); + } drop(db); - let mut results = HandleResult::rpc(request.to_rpc()); let me = state.group.read().await.clone_user(&gid)?; - let join_proof = JoinProof::Open(me.name, me.avatar); - let data = postcard::to_allocvec(&LayerEvent::Request(request.gid, join_proof)) - .unwrap_or(vec![]); - let s = SendType::Event(0, request.addr, data); + let join_proof = match gtype { + GroupType::Encrypted => { + // remark is inviter did. + let _fgid = GroupId::from_hex(gremark)?; + // TODO + JoinProof::Zkp(proof) + } + GroupType::Private => { + // remark is inviter did. + let fgid = GroupId::from_hex(gremark)?; + JoinProof::Invite(fgid, proof, me.name, me.avatar) + } + GroupType::Open => JoinProof::Open(me.name, me.avatar), + }; + + let data = + postcard::to_allocvec(&LayerEvent::Request(gcd, join_proof)).unwrap_or(vec![]); + let s = SendType::Event(0, gaddr, data); add_layer(&mut results, gid, s); Ok(results) }, @@ -297,12 +322,13 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler) { let mut layer_lock = state.layer.write().await; for (fid, fgid, mut faddr, proof) in invites { let contact_values = format!( - "{};;{};;{};;{};;{}", + "{};;{};;{};;{};;{};;{}", gc.g_type.to_u32(), gcd.to_hex(), gc.g_addr.to_hex(), tmp_name, proof.to_hex(), + gc.key.to_hex(), ); // check if encrypted group type. need online.