diff --git a/lib/apps/group_chat/add.dart b/lib/apps/group_chat/add.dart index 32001a2..3b66eed 100644 --- a/lib/apps/group_chat/add.dart +++ b/lib/apps/group_chat/add.dart @@ -16,7 +16,6 @@ import 'package:esse/widgets/qr_scan.dart'; import 'package:esse/global.dart'; import 'package:esse/provider.dart'; -import 'package:esse/apps/chat/models.dart' show Request; // TODO delete. import 'package:esse/apps/group_chat/models.dart'; import 'package:esse/apps/group_chat/provider.dart'; @@ -112,7 +111,7 @@ class _GroupAddPageState extends State { return; } - if (id.substring(0, 2) == 'EH') { + if (id.substring(0, 2) == 'EG') { id = id.substring(2); } @@ -123,8 +122,7 @@ class _GroupAddPageState extends State { } var name = _joinNameController.text; var remark = _joinRemarkController.text; - - //context.read().requestCreate(Request(id, addr, name, remark)); + context.read().join(id, addr, name, remark); setState(() { _joinIdController.text = ''; _joinAddrController.text = ''; @@ -135,7 +133,11 @@ class _GroupAddPageState extends State { _create() { final myName = context.read().activedAccount.name; - final addr = _createAddrController.text.trim(); + var addr = _createAddrController.text.trim(); + // if has 0x, need remove + if (addr.substring(0, 2) == '0x') { + addr = addr.substring(2); + } final name = _createNameController.text.trim(); final bio = _createBioController.text.trim(); context.read().create(myName, addr, name, bio, _groupNeedAgree); @@ -520,7 +522,8 @@ class _RequestItem extends StatelessWidget { const SizedBox(height: 10.0), const Divider(height: 1.0, color: Color(0x40ADB0BB)), const SizedBox(height: 10.0), - _infoListTooltip(Icons.person, color.primary, 'EH' + request.gid.toUpperCase()), + _infoListTooltip(Icons.person, color.primary, + (request.isMe ? 'EG' : 'EH') + request.gid.toUpperCase()), _infoListTooltip(Icons.location_on, color.primary, "0x" + request.addr), _infoList(Icons.turned_in, color.primary, request.remark), _infoList(Icons.access_time_rounded, color.primary, request.time.toString()), diff --git a/lib/apps/group_chat/models.dart b/lib/apps/group_chat/models.dart index 0c13c08..3898d49 100644 --- a/lib/apps/group_chat/models.dart +++ b/lib/apps/group_chat/models.dart @@ -135,6 +135,42 @@ class GroupChat { } } +class Request { + int id; + int fid; + String gid; + String addr; + String name; + String remark; + bool ok; + bool over; + RelativeTime time; + + bool get isMe => this.fid == 0; + + Avatar showAvatar([double width = 45.0]) { + final avatar = Global.avatarPath + this.gid + '.png'; + return Avatar( + width: width, + name: this.name, + avatarPath: avatar, + needOnline: false, + ); + } + + Request.fromList(List params) { + this.id = params[0]; + this.fid = params[1]; + this.gid = params[2]; + this.addr = params[3]; + this.name = params[4]; + this.remark = params[5]; + this.ok = params[6]; + this.over = params[7]; + this.time = RelativeTime.fromInt(params[8]); + } +} + class Member { int id; int fid; diff --git a/lib/apps/group_chat/provider.dart b/lib/apps/group_chat/provider.dart index 901d70e..bd66a87 100644 --- a/lib/apps/group_chat/provider.dart +++ b/lib/apps/group_chat/provider.dart @@ -15,7 +15,7 @@ class GroupChatProvider extends ChangeNotifier { Map groups = {}; List createKeys = []; List orderKeys = []; - SplayTreeMap requests = SplayTreeMap(); + SplayTreeMap requests = SplayTreeMap(); int actived; SplayTreeMap activedMessages = SplayTreeMap(); @@ -42,11 +42,11 @@ class GroupChatProvider extends ChangeNotifier { rpc.addListener('group-chat-result', _result, false); rpc.addListener('group-chat-detail', _detail, true); // rpc.addListener('group-chat-update', _update, false); - // rpc.addListener('group-chat-join', _join, true); + rpc.addListener('group-chat-join', _join, true); // rpc.addListener('group-chat-agree', _agree, true); // rpc.addListener('group-chat-reject', _reject, false); rpc.addListener('group-chat-member-join', _memberJoin, false); - // rpc.addListener('group-chat-member-info', _memberInfo, false); + rpc.addListener('group-chat-member-info', _memberInfo, false); // rpc.addListener('group-chat-member-leave', _memberLeave, false); rpc.addListener('group-chat-member-online', _memberOnline, false); rpc.addListener('group-chat-member-offline', _memberOffline, false); @@ -86,6 +86,7 @@ class GroupChatProvider extends ChangeNotifier { } create(String myName, String addr, String name, String bio, bool needAgree) { + print(addr); rpc.send('group-chat-create', [myName, addr, name, bio, needAgree]); } @@ -93,6 +94,10 @@ class GroupChatProvider extends ChangeNotifier { // } + join(String gid, String gaddr, String name, String remark) { + rpc.send('group-chat-join', [gid, gaddr, name, remark]); + } + messageCreate(MessageType mtype, String content) { final gid = this.activedGroup.gid; rpc.send('group-chat-message-create', [gid, mtype.toInt(), content]); @@ -173,8 +178,27 @@ class GroupChatProvider extends ChangeNotifier { } } + _join(List params) { + this.requests[params[0]] = Request.fromList(params); + notifyListeners(); + } + _memberJoin(List params) { - // + final member = Member.fromList(params); + if (this.actived == member.fid) { + this.activedMembers[member.id] = member; + // TODO Better add UI member joined. + notifyListeners(); + } + } + + _memberInfo(List params) { + final id = params[0]; + if (this.activedMembers.containsKey(id)) { + this.activedMembers[id].addr = params[1]; + this.activedMembers[id].name = params[2]; + notifyListeners(); + } } _memberOnline(List params) { diff --git a/pubspec.lock b/pubspec.lock index c52cfc9..afb0123 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,7 +21,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.6.0" + version: "2.6.1" audio_session: dependency: transitive description: diff --git a/src/apps/group_chat/layer.rs b/src/apps/group_chat/layer.rs index 9aa80e2..a2eb3be 100644 --- a/src/apps/group_chat/layer.rs +++ b/src/apps/group_chat/layer.rs @@ -13,7 +13,7 @@ use group_chat_types::{Event, GroupConnect, GroupResult, JoinProof, LayerEvent}; use tdn_did::Proof; use crate::layer::{Layer, Online}; -use crate::storage::group_chat_db; +use crate::storage::{group_chat_db, write_avatar_sync}; use super::models::{from_network_message, GroupChat, Member}; use super::{add_layer, rpc}; @@ -146,20 +146,32 @@ async fn handle_event( results.rpcs.push(rpc::group_online(mgid, gid)); } LayerEvent::Sync(_, height, event) => { + let base = layer.read().await.base().clone(); + let db = group_chat_db(&base, &mgid)?; + match event { Event::GroupInfo => {} Event::GroupTransfer => {} Event::GroupManagerAdd => {} Event::GroupManagerDel => {} Event::GroupClose => {} - Event::MemberInfo(mid, maddr, mname, mavatar) => {} + Event::MemberInfo(mid, maddr, mname, mavatar) => { + let id = Member::get_id(&db, &gid, &mid)?; + Member::update(&db, &id, &maddr, &mname)?; + if mavatar.len() > 0 { + write_avatar_sync(&base, &mgid, &mid, mavatar)?; + } + results.rpcs.push(rpc::member_info(mgid, id, maddr, mname)); + } Event::MemberJoin(mid, maddr, mname, mavatar, mtime) => { - let db = group_chat_db(layer.read().await.base(), &mgid)?; let mut member = Member::new(gid, mid, maddr, mname, false, mtime); member.insert(&db)?; + if mavatar.len() > 0 { + write_avatar_sync(&base, &mgid, &mid, mavatar)?; + } results.rpcs.push(rpc::member_join(mgid, member)); } - Event::MemberLeave(mid) => {} + Event::MemberLeave(_mid) => {} Event::MessageCreate(mid, nmsg, mtime) => { let base = layer.read().await.base.clone(); let msg = @@ -169,8 +181,7 @@ async fn handle_event( } // save event. - - // update to UI. + GroupChat::add_height(&db, gid, height)?; } LayerEvent::MemberOnline(_, mid, maddr) => { results.rpcs.push(rpc::member_online(mgid, gid, mid, maddr)); diff --git a/src/apps/group_chat/models.rs b/src/apps/group_chat/models.rs index 3039402..e64fd78 100644 --- a/src/apps/group_chat/models.rs +++ b/src/apps/group_chat/models.rs @@ -274,6 +274,11 @@ impl GroupChat { db.update(&sql) } + pub fn add_height(db: &DStorage, id: i64, height: i64) -> Result { + let sql = format!("UPDATE groups SET height={} WHERE id = {}", height, id,); + db.update(&sql) + } + pub fn update_last_message(db: &DStorage, id: i64, msg: &Message, read: bool) -> Result { let sql = format!( "UPDATE groups SET last_datetime={}, last_content='{}', last_readed={} WHERE id = {}", @@ -291,6 +296,94 @@ impl GroupChat { } } +/// Group Join Request model. include my requests and other requests. +/// When fid is 0, it's my requests. +pub(crate) struct Request { + id: i64, + fid: i64, + pub gid: GroupId, + pub addr: PeerAddr, + pub name: String, + remark: String, + is_ok: bool, + is_over: bool, + datetime: i64, +} + +impl Request { + pub fn new_by_remote( + fid: i64, + gid: GroupId, + addr: PeerAddr, + name: String, + remark: String, + datetime: i64, + ) -> Self { + Self { + fid, + gid, + addr, + name, + remark, + datetime, + is_ok: false, + is_over: false, + id: 0, + } + } + + pub fn new_by_me(gid: GroupId, addr: PeerAddr, name: String, remark: String) -> Self { + let start = SystemTime::now(); + let datetime = start + .duration_since(UNIX_EPOCH) + .map(|s| s.as_secs()) + .unwrap_or(0) as i64; // safe for all life. + + Self { + gid, + addr, + name, + remark, + datetime, + is_ok: false, + is_over: false, + fid: 0, + id: 0, + } + } + + pub fn to_rpc(&self) -> RpcParam { + json!([ + self.id, + self.fid, + self.gid.to_hex(), + self.addr.to_hex(), + self.name, + self.remark, + self.is_ok, + self.is_over, + self.datetime, + ]) + } + + pub fn insert(&mut self, db: &DStorage) -> Result<()> { + let sql = format!("INSERT INTO requests (fid, gid, addr, name, remark, is_ok, is_over, datetime, is_deleted) VALUES ({}, '{}', '{}', '{}', '{}', {}, {}, {}, false)", + self.fid, + self.gid.to_hex(), + self.addr.to_hex(), + self.name, + self.remark, + if self.is_ok { 1 } else { 0 }, + if self.is_over { 1 } else { 0 }, + self.datetime, + ); + println!("{}", sql); + let id = db.insert(&sql)?; + self.id = id; + Ok(()) + } +} + /// Group Member Model. pub(crate) struct Member { /// db auto-increment id. @@ -398,6 +491,16 @@ impl Member { Err(new_io_error("missing member")) } } + + pub fn update(db: &DStorage, id: &i64, addr: &PeerAddr, name: &str) -> Result { + let sql = format!( + "UPDATE members SET addr='{}', name='{}' WHERE id = {}", + addr.to_hex(), + name, + id, + ); + db.update(&sql) + } } /// Group Chat Message Model. diff --git a/src/apps/group_chat/rpc.rs b/src/apps/group_chat/rpc.rs index 1943079..acd2d64 100644 --- a/src/apps/group_chat/rpc.rs +++ b/src/apps/group_chat/rpc.rs @@ -7,14 +7,14 @@ use tdn::types::{ }; use tdn_did::Proof; -use group_chat_types::{CheckType, Event, GroupConnect, GroupType, LayerEvent, NetworkMessage}; +use group_chat_types::{CheckType, Event, GroupConnect, GroupType, JoinProof, LayerEvent}; use crate::apps::chat::MessageType; use crate::rpc::RpcState; use crate::storage::group_chat_db; use super::add_layer; -use super::models::{to_network_message, GroupChat, Member, Message}; +use super::models::{to_network_message, GroupChat, Member, Message, Request}; #[inline] pub(crate) fn create_check(mgid: GroupId, ct: CheckType, supported: Vec) -> RpcParam { @@ -42,6 +42,16 @@ pub(crate) fn member_join(mgid: GroupId, member: Member) -> RpcParam { rpc_response(0, "group-chat-member-join", json!(member.to_rpc()), mgid) } +#[inline] +pub(crate) fn member_info(mgid: GroupId, id: i64, addr: PeerAddr, name: String) -> RpcParam { + rpc_response( + 0, + "group-chat-member-info", + json!([id, addr.to_hex(), name]), + mgid, + ) +} + #[inline] pub(crate) fn member_online(mgid: GroupId, gid: i64, mid: GroupId, maddr: PeerAddr) -> RpcParam { rpc_response( @@ -129,9 +139,7 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler) { handler.add_method( "group-chat-check", |gid: GroupId, params: Vec, _state: Arc| async move { - let addr = PeerAddr::from_hex(params[0].as_str()?) - .map_err(|_e| new_io_error("PeerAddr invalid!"))?; - println!("addr: {}", addr.to_hex()); + let addr = PeerAddr::from_hex(params[0].as_str()?)?; let mut results = HandleResult::new(); let data = postcard::to_allocvec(&GroupConnect::Check).unwrap_or(vec![]); @@ -145,8 +153,7 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler) { "group-chat-create", |gid: GroupId, params: Vec, state: Arc| async move { let my_name = params[0].as_str()?.to_owned(); - let addr = PeerAddr::from_hex(params[1].as_str()?) - .map_err(|_e| new_io_error("PeerAddr invalid!"))?; + let addr = PeerAddr::from_hex(params[1].as_str()?)?; let name = params[2].as_str()?.to_owned(); let bio = params[3].as_str()?.to_owned(); let need_agree = params[4].as_bool()?; @@ -178,6 +185,30 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler) { }, ); + handler.add_method( + "group-chat-join", + |gid: GroupId, params: Vec, state: Arc| async move { + let gcd = GroupId::from_hex(params[0].as_str()?)?; + let gaddr = PeerAddr::from_hex(params[1].as_str()?)?; + let gname = params[2].as_str()?.to_owned(); + let gremark = params[3].as_str()?.to_owned(); + + let mut request = Request::new_by_me(gcd, gaddr, gname, gremark); + let db = group_chat_db(state.layer.read().await.base(), &gid)?; + request.insert(&db)?; + 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(&GroupConnect::Join(request.gid, join_proof)) + .unwrap_or(vec![]); + let s = SendType::Connect(0, request.addr, None, None, data); + add_layer(&mut results, gid, s); + Ok(results) + }, + ); + handler.add_method( "group-chat-message-create", |gid: GroupId, params: Vec, state: Arc| async move { diff --git a/src/migrate/group_chat.rs b/src/migrate/group_chat.rs index d105e81..f2b54c4 100644 --- a/src/migrate/group_chat.rs +++ b/src/migrate/group_chat.rs @@ -22,7 +22,7 @@ pub(super) const GROUP_CHAT_VERSIONS: [&str; 4] = [ "CREATE TABLE IF NOT EXISTS requests( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, fid INTEGER NOT NULL, - mid TEXT NOT NULL, + gid TEXT NOT NULL, addr TEXT NOT NULL, name TEXT NOT NULL, remark TEXT,