Browse Source

add invite message type

pull/18/head
Sun 4 years ago
parent
commit
259613cd1c
  1. 2
      lib/apps/group_chat/add.dart
  2. 208
      lib/apps/group_chat/detail.dart
  3. 41
      lib/apps/group_chat/provider.dart
  4. 5
      lib/apps/primitives.dart
  5. 6
      lib/l10n/localizations.dart
  6. 12
      lib/l10n/localizations_en.dart
  7. 12
      lib/l10n/localizations_zh.dart
  8. 22
      lib/widgets/chat_message.dart
  9. 117
      lib/widgets/show_contact.dart
  10. 41
      pubspec.lock
  11. 3
      src/apps/chat/layer.rs
  12. 2
      src/apps/chat/mod.rs
  13. 6
      src/apps/chat/models.rs
  14. 2
      src/apps/group_chat/models.rs
  15. 86
      src/apps/group_chat/rpc.rs

2
lib/apps/group_chat/add.dart

@ -144,7 +144,7 @@ class _GroupAddPageState extends State<GroupAddPage> {
addr = addr.substring(2); addr = addr.substring(2);
} }
var name = _joinNameController.text; var name = _joinNameController.text;
context.read<GroupChatProvider>().join(id, addr, name, ""); context.read<GroupChatProvider>().join(GroupType.Open, id, addr, name, "");
setState(() { setState(() {
_joinIdController.text = ''; _joinIdController.text = '';
_joinAddrController.text = ''; _joinAddrController.text = '';

208
lib/apps/group_chat/detail.dart

@ -14,6 +14,8 @@ import 'package:esse/widgets/shadow_dialog.dart';
import 'package:esse/widgets/audio_recorder.dart'; import 'package:esse/widgets/audio_recorder.dart';
import 'package:esse/widgets/user_info.dart'; import 'package:esse/widgets/user_info.dart';
import 'package:esse/widgets/chat_message.dart'; import 'package:esse/widgets/chat_message.dart';
import 'package:esse/widgets/show_contact.dart';
import 'package:esse/rpc.dart';
import 'package:esse/global.dart'; import 'package:esse/global.dart';
import 'package:esse/provider.dart'; import 'package:esse/provider.dart';
@ -214,7 +216,7 @@ class _GroupChatDetailState extends State<GroupChatDetail> {
return Scaffold( return Scaffold(
key: GroupChatDetail._scaffoldKey, key: GroupChatDetail._scaffoldKey,
endDrawer: _MemberDrawerWidget(title: lang.members), endDrawer: _MemberDrawerWidget(gid: this.group.gid, title: lang.members),
drawerScrimColor: color.background, drawerScrimColor: color.background,
body: SafeArea( body: SafeArea(
child: Column( child: Column(
@ -588,24 +590,38 @@ Widget _menuItem(Color color, int value, IconData icon, String text) {
} }
class _MemberDrawerWidget extends StatelessWidget { class _MemberDrawerWidget extends StatelessWidget {
final String gid;
final String title; final String title;
const _MemberDrawerWidget({Key key, this.title}) : super(key: key); const _MemberDrawerWidget({Key key, this.gid, this.title}) : super(key: key);
Widget _item(context, Member member, bool isOwner, Color color) { Widget _meItem(Member member, bool meOwner, bool meManager, Color color, lang) {
return Container(
height: 55.0,
child: ListTile(
leading: member.showAvatar(colorSurface: false),
title: Text(lang.me, textAlign: TextAlign.left,
style: TextStyle(fontSize: 16.0, fontStyle: FontStyle.italic)),
trailing: Text(meOwner ? lang.groupOwner : (meManager ? lang.manager : ''),
style: TextStyle(color: color)),
)
);
}
Widget _item(context, Member member, bool isOwner, bool meOwner, bool meManager, Color color, lang) {
return Container( return Container(
height: 55.0, height: 55.0,
child: ListTile( child: ListTile(
leading: member.showAvatar(colorSurface: false), leading: member.showAvatar(colorSurface: false),
title: Text(member.name, textAlign: TextAlign.left, style: TextStyle(fontSize: 16.0)), title: Text(member.name, textAlign: TextAlign.left, style: TextStyle(fontSize: 16.0)),
trailing: Text(member.isBlock trailing: Text(member.isBlock
? 'Blocked' : (isOwner ? lang.blocked : (isOwner
? 'Owner' : (member.isManager ? lang.groupOwner : (member.isManager
? 'Manager' : '')), ? lang.manager : '')),
style: TextStyle(color: color)), style: TextStyle(color: color)),
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
showShadowDialog(context, Icons.group_rounded, title, showShadowDialog(context, Icons.group_rounded, title,
MemberDetail(member: member, isGroupOwner: isOwner, isGroupManager: member.isManager), MemberDetail(member: member, isGroupOwner: meOwner, isGroupManager: meManager),
10.0, 10.0,
); );
} }
@ -613,16 +629,33 @@ class _MemberDrawerWidget extends StatelessWidget {
); );
} }
_action(List<int> ids) {
rpc.send('group-chat-invite', [gid, ids]);
}
_invite(context, String title) {
Navigator.pop(context);
showShadowDialog(context, Icons.people_rounded, title,
ContactList(callback: _action, multiple: true),
0.0,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme; final color = Theme.of(context).colorScheme;
final lang = AppLocalizations.of(context); final lang = AppLocalizations.of(context);
final isLight = color.brightness == Brightness.light; final isLight = color.brightness == Brightness.light;
final isDesktop = isDisplayDesktop(context); final isDesktop = isDisplayDesktop(context);
final myId = context.read<AccountProvider>().activedAccountId;
final provider = context.watch<GroupChatProvider>(); final provider = context.watch<GroupChatProvider>();
final members = provider.activedMembers; final members = provider.activedMembers;
final allKeys = provider.activedMemberOrder; final all = provider.activedMemberOrder(myId);
final allKeys = all[0];
final meId = all[1];
final meOwner = all[2];
final meManager = all[3];
return Drawer( return Drawer(
child: BackdropFilter( child: BackdropFilter(
@ -633,15 +666,41 @@ class _MemberDrawerWidget extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 20.0), padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Column( child: Column(
children: [ children: [
Text(lang.members, style: Theme.of(context).textTheme.title), Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.only(left: 20.0),
child: Text("${lang.members} (${allKeys.length + 1})",
style: Theme.of(context).textTheme.title),
),
Container(
margin: const EdgeInsets.only(right: 10.0),
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 10.0),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFF6174FF)),
borderRadius: BorderRadius.circular(25.0)),
child: TextButton(child: Row(
children: [
Icon(Icons.add, size: 16.0),
Text(lang.invite),
]
),
onPressed: () => _invite(context, lang.contact),
),
)
]
),
const SizedBox(height: 10.0), const SizedBox(height: 10.0),
const Divider(height: 1.0, color: Color(0x40ADB0BB)), const Divider(height: 1.0, color: Color(0x40ADB0BB)),
const SizedBox(height: 10.0), const SizedBox(height: 10.0),
_meItem(members[meId], meOwner, meManager, color.primary, lang),
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
itemCount: allKeys.length, itemCount: allKeys.length,
itemBuilder: (BuildContext ctx, int index) => _item( itemBuilder: (BuildContext ctx, int index) => _item(
context, members[allKeys[index]], index == 0, color.primary context, members[allKeys[index]],
index == 0 && !meOwner, meOwner, meManager, color.primary, lang
), ),
) )
) )
@ -702,75 +761,84 @@ class _MemberDetailState extends State<MemberDetail> {
const SizedBox(height: 10.0), const SizedBox(height: 10.0),
_infoListTooltip(Icons.person, color.primary, widget.member.mid), _infoListTooltip(Icons.person, color.primary, widget.member.mid),
_infoListTooltip(Icons.location_on, color.primary, widget.member.addr), _infoListTooltip(Icons.location_on, color.primary, widget.member.addr),
const SizedBox(height: 10.0),
if (widget.isGroupOwner) if (widget.isGroupOwner)
InkWell( Container(
onTap: () { padding: const EdgeInsets.only(top: 20.0, bottom: 10.0),
Navigator.pop(context); child: InkWell(
// TODO delete. onTap: () {
}, Navigator.pop(context);
hoverColor: Colors.transparent, // TODO delete.
child: Container( },
width: 300.0, hoverColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 10.0), child: Container(
decoration: BoxDecoration( width: 300.0,
border: Border.all(), padding: const EdgeInsets.symmetric(vertical: 10.0),
borderRadius: BorderRadius.circular(10.0)), decoration: BoxDecoration(
child: Center(child: Text(widget.member.isManager ? 'Cancel Manager' : 'Set Manager', border: Border.all(color: color.primary),
style: TextStyle(fontSize: 14.0))), borderRadius: BorderRadius.circular(10.0)),
child: Center(child: Text(widget.member.isManager ? 'Cancel Manager' : 'Set Manager',
style: TextStyle(fontSize: 14.0, color: color.primary))),
)
) )
), ),
const SizedBox(height: 10.0),
if (notFriend) if (notFriend)
InkWell( Container(
onTap: () { padding: const EdgeInsets.symmetric(vertical: 10.0),
Navigator.pop(context); child: InkWell(
// TODO delete. onTap: () {
}, Navigator.pop(context);
hoverColor: Colors.transparent, // TODO delete.
child: Container( },
width: 300.0, hoverColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 10.0), child: Container(
decoration: BoxDecoration( width: 300.0,
border: Border.all(color: Color(0xFF6174FF)), padding: const EdgeInsets.symmetric(vertical: 10.0),
borderRadius: BorderRadius.circular(10.0)), decoration: BoxDecoration(
child: Center(child: Text(lang.addFriend, border: Border.all(color: Color(0xFF6174FF)),
style: TextStyle(fontSize: 14.0, color: Color(0xFF6174FF)))), borderRadius: BorderRadius.circular(10.0)),
child: Center(child: Text(lang.addFriend,
style: TextStyle(fontSize: 14.0, color: Color(0xFF6174FF)))),
)
) )
), ),
const SizedBox(height: 10.0),
if (widget.isGroupManager || widget.isGroupOwner) if (widget.isGroupManager || widget.isGroupOwner)
InkWell( Container(
onTap: () { padding: const EdgeInsets.symmetric(vertical: 10.0),
Navigator.pop(context); child: InkWell(
// TODO delete. onTap: () {
}, Navigator.pop(context);
hoverColor: Colors.transparent, // TODO delete.
child: Container( },
width: 300.0, hoverColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 10.0), child: Container(
decoration: BoxDecoration( width: 300.0,
border: Border.all(color: Colors.red), padding: const EdgeInsets.symmetric(vertical: 10.0),
borderRadius: BorderRadius.circular(10.0)), decoration: BoxDecoration(
child: Center(child: Text(lang.delete, border: Border.all(color: Colors.red),
style: TextStyle(fontSize: 14.0, color: Colors.red))), borderRadius: BorderRadius.circular(10.0)),
child: Center(child: Text(lang.delete,
style: TextStyle(fontSize: 14.0, color: Colors.red))),
)
) )
), ),
InkWell( Container(
onTap: () { padding: const EdgeInsets.symmetric(vertical: 10.0),
Navigator.pop(context); child: InkWell(
context.read<GroupChatProvider>().memberUpdate( onTap: () {
widget.member.id, !widget.member.isBlock); Navigator.pop(context);
}, context.read<GroupChatProvider>().memberUpdate(
hoverColor: Colors.transparent, widget.member.id, !widget.member.isBlock);
child: Container( },
width: 300.0, hoverColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 10.0), child: Container(
decoration: BoxDecoration( width: 300.0,
border: Border.all(color: Colors.red), padding: const EdgeInsets.symmetric(vertical: 10.0),
borderRadius: BorderRadius.circular(10.0)), decoration: BoxDecoration(
child: Center(child: Text(widget.member.isBlock ? 'Blocked' : 'Block', border: Border.all(color: Colors.black),
style: TextStyle(fontSize: 14.0, color: Colors.red))), borderRadius: BorderRadius.circular(10.0)),
child: Center(child: Text(widget.member.isBlock ? lang.blocked : lang.block,
style: TextStyle(fontSize: 14.0, color: Colors.black))),
)
) )
), ),
] ]

41
lib/apps/group_chat/provider.dart

@ -33,27 +33,40 @@ class GroupChatProvider extends ChangeNotifier {
return false; return false;
} }
List<int> get activedMemberOrder { List activedMemberOrder(String myId) {
int meId = 0;
bool meOwner = false;
bool meManager = false;
List<int> allKeys = []; List<int> allKeys = [];
List<int> managers = []; List<int> managers = [];
List<int> commons = []; List<int> commons = [];
List<int> blocks = []; List<int> blocks = [];
this.activedMembers.forEach((i, m) { this.activedMembers.forEach((i, m) {
if (m.isBlock) { if (m.mid == myId) {
blocks.add(i); meId = i;
} else {
if (m.isManager) { if (m.isManager) {
meManager = true;
if (m.mid == this.activedGroup.owner) { if (m.mid == this.activedGroup.owner) {
allKeys.add(i); meOwner = true;
} else {
managers.add(i);
} }
}
} else {
if (m.isBlock) {
blocks.add(i);
} else { } else {
commons.add(i); if (m.isManager) {
if (m.mid == this.activedGroup.owner) {
allKeys.add(i);
} else {
managers.add(i);
}
} else {
commons.add(i);
}
} }
} }
}); });
return allKeys + managers + commons + blocks; return [allKeys + managers + commons + blocks, meId, meOwner, meManager];
} }
GroupChatProvider() { GroupChatProvider() {
@ -117,8 +130,8 @@ class GroupChatProvider extends ChangeNotifier {
rpc.send('group-chat-resend', [id, myName]); rpc.send('group-chat-resend', [id, myName]);
} }
join(String gid, String gaddr, String name, String remark, [String key = '']) { join(GroupType gtype, String gid, String gaddr, String name, String remark, [String proof = '', String key = '']) {
rpc.send('group-chat-join', [gid, gaddr, name, remark, key]); rpc.send('group-chat-join', [gtype.toInt(), gid, gaddr, name, remark, proof, key]);
} }
requestHandle(String gid, int id, int rid, bool ok) { requestHandle(String gid, int id, int rid, bool ok) {
@ -150,6 +163,12 @@ class GroupChatProvider extends ChangeNotifier {
// rpc.send('group-chat-readd', [id]); // rpc.send('group-chat-readd', [id]);
} }
invite(String gid, List<int> ids) {
print(gid);
print(ids);
rpc.send('group-chat-invite', [ids]);
}
memberUpdate(int id, bool isBlock) { memberUpdate(int id, bool isBlock) {
rpc.send('group-chat-member-update', [id, isBlock]); rpc.send('group-chat-member-update', [id, isBlock]);
} }

5
lib/apps/primitives.dart

@ -11,6 +11,7 @@ enum MessageType {
Record, Record,
Phone, Phone,
Video, Video,
Invite,
} }
// use 00-99 // use 00-99
@ -33,6 +34,8 @@ extension MessageTypeExtension on MessageType {
return 6; return 6;
case MessageType.Video: case MessageType.Video:
return 7; return 7;
case MessageType.Invite:
return 8;
default: default:
return 0; return 0;
} }
@ -56,6 +59,8 @@ extension MessageTypeExtension on MessageType {
return MessageType.Phone; return MessageType.Phone;
case 7: case 7:
return MessageType.Video; return MessageType.Video;
case 8:
return MessageType.Invite;
default: default:
return MessageType.String; return MessageType.String;
} }

6
lib/l10n/localizations.dart

@ -82,6 +82,11 @@ abstract class AppLocalizations {
String get create; String get create;
String get exit; String get exit;
String get loadMore; String get loadMore;
String get me;
String get manager;
String get block;
String get blocked;
String get invite;
// theme // theme
String get themeDark; String get themeDark;
@ -175,6 +180,7 @@ abstract class AppLocalizations {
String get groupChatBio; String get groupChatBio;
String get groupJoin; String get groupJoin;
String get groupCreate; String get groupCreate;
String get groupOwner;
String get groupTypeEncrypted; String get groupTypeEncrypted;
String get groupTypeEncryptedInfo; String get groupTypeEncryptedInfo;
String get groupTypePrivate; String get groupTypePrivate;

12
lib/l10n/localizations_en.dart

@ -88,6 +88,16 @@ class AppLocalizationsEn extends AppLocalizations {
String get exit => 'Exit'; String get exit => 'Exit';
@override @override
String get loadMore => 'Load more...'; String get loadMore => 'Load more...';
@override
String get me => 'Me';
@override
String get manager => 'Manager';
@override
String get block => 'Block';
@override
String get blocked => 'Blocked';
@override
String get invite => 'Invite';
// theme // theme
@override @override
@ -261,6 +271,8 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get groupCreate => 'Create Group'; String get groupCreate => 'Create Group';
@override @override
String get groupOwner => 'Owner';
@override
String get groupTypeEncrypted => 'Encrypted'; String get groupTypeEncrypted => 'Encrypted';
@override @override
String get groupTypeEncryptedInfo => "Encrypted: It can only be joined by the invitation of members, and the manager's consent is optional, Members hold a zero-knowledge proof about the key to enter; Group information and messages are encrypted and stored on the server, and the server NOT has secret key, INVISIBLE information."; String get groupTypeEncryptedInfo => "Encrypted: It can only be joined by the invitation of members, and the manager's consent is optional, Members hold a zero-knowledge proof about the key to enter; Group information and messages are encrypted and stored on the server, and the server NOT has secret key, INVISIBLE information.";

12
lib/l10n/localizations_zh.dart

@ -88,6 +88,16 @@ class AppLocalizationsZh extends AppLocalizations {
String get exit => '退出'; String get exit => '退出';
@override @override
String get loadMore => '加载更多'; String get loadMore => '加载更多';
@override
String get me => '';
@override
String get manager => '管理员';
@override
String get block => '拉黑';
@override
String get blocked => '已拉黑';
@override
String get invite => '邀请';
// theme // theme
@override @override
@ -261,6 +271,8 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get groupCreate => '新建群'; String get groupCreate => '新建群';
@override @override
String get groupOwner => '群主';
@override
String get groupTypeEncrypted => '加密'; String get groupTypeEncrypted => '加密';
@override @override
String get groupTypeEncryptedInfo => '加密的群聊:只能通过群成员邀请加入,可选是否需要管理员同意,成员需持有密钥的零知识证明方可进入;群信息和消息全部加密存储在服务端,服务端无密钥,不可见信息。'; String get groupTypeEncryptedInfo => '加密的群聊:只能通过群成员邀请加入,可选是否需要管理员同意,成员需持有密钥的零知识证明方可进入;群信息和消息全部加密存储在服务端,服务端无密钥,不可见信息。';

22
lib/widgets/chat_message.dart

@ -308,6 +308,26 @@ class ChatMessage extends StatelessWidget {
} }
} }
Widget _showInvite(context, lang, color) {
// contact [name, gid, addr, avatar]
//final infos = message.showContact();
//final gid = 'EG' + infos[1].toUpperCase();
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)));
}
Widget _infoListTooltip(icon, color, text) { Widget _infoListTooltip(icon, color, text) {
return Container( return Container(
width: 300.0, width: 300.0,
@ -338,6 +358,8 @@ class ChatMessage extends StatelessWidget {
return _showContact(context, lang, color); return _showContact(context, lang, color);
} else if (message.type == MessageType.Record) { } else if (message.type == MessageType.Record) {
return _showRecord(); return _showRecord();
} else if (message.type == MessageType.Invite) {
return _showInvite(context, lang, color);
} }
return _showText(context, color, isDesktop); return _showText(context, color, isDesktop);
} }

117
lib/widgets/show_contact.dart

@ -1,12 +1,65 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:esse/l10n/localizations.dart'; import 'package:esse/l10n/localizations.dart';
import 'package:esse/models/friend.dart'; import 'package:esse/apps/chat/models.dart' show Friend;
import 'package:esse/widgets/shadow_dialog.dart'; import 'package:esse/apps/chat/provider.dart';
openContact(BuildContext context, ColorScheme color, AppLocalizations lang, List<int> orders, Map<int, Friend> friends, Function callback) { class ContactList extends StatefulWidget {
showShadowDialog( final Function callback;
context, Icons.person_rounded, lang.contact, Column( final bool multiple;
const ContactList({Key key, this.callback, this.multiple}): super(key: key);
@override
_ContactListState createState() => _ContactListState();
}
class _ContactListState extends State<ContactList> {
List<bool> _checks = [];
Map<int, Friend> _friends = {};
List<int> _keys = [];
@override
initState() {
super.initState();
new Future.delayed(Duration.zero, () {
final provider = context.read<ChatProvider>();
_friends = provider.friends;
_keys = provider.orderKeys;
_checks = List<bool>.generate(_keys.length, (_) => false);
setState(() {});
});
}
Widget _friend(int i, Friend friend) {
return Container(
height: 55.0,
child: ListTile(
leading: friend.showAvatar(),
title: Text(friend.name),
trailing: Checkbox(
value: _checks[i],
onChanged: (bool value) {
setState(() {
_checks[i] = value;
});
},
),
)
);
}
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme;
final lang = AppLocalizations.of(context);
double maxHeight = (MediaQuery.of(context).size.height - 300);
if (maxHeight < 100.0) {
maxHeight = 100.0;
}
return Column(
children: [ children: [
Container( Container(
height: 40.0, height: 40.0,
@ -29,28 +82,38 @@ openContact(BuildContext context, ColorScheme color, AppLocalizations lang, List
), ),
), ),
), ),
SizedBox(height: 15.0), const SizedBox(height: 16.0),
Column( Container(
children: orders.map<Widget>((id) { height: maxHeight,
final contact = friends[id]; child: SingleChildScrollView(
return GestureDetector( child: Column(children: List<Widget>.generate(_keys.length, (i) =>
behavior: HitTestBehavior.opaque, _friend(i, _friends[_keys[i]])
onTap: () => callback(context, contact.id), ))
child: Padding( )
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 14.0), ),
child: Row( const Divider(height: 1.0, color: Color(0x40ADB0BB)),
children: [ const SizedBox(height: 10.0),
contact.showAvatar(needOnline: false), Row(
SizedBox(width: 15.0), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Text(contact.name, style: TextStyle(fontSize: 16.0)), children: [
], TextButton(child: Text(lang.cancel, style: TextStyle(color: color.onSurface)),
), onPressed: () => Navigator.pop(context),
), ),
); TextButton(child: Text(lang.ok),
}).toList() onPressed: () {
Navigator.pop(context);
List<int> ids = [];
_keys.asMap().forEach((i, value) {
if (_checks[i]) {
ids.add(value);
}
});
widget.callback(ids);
},
),
]
) )
] ]
) );
); }
} }

41
pubspec.lock

@ -21,14 +21,14 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.6.1" version: "2.7.0"
audio_session: audio_session:
dependency: transitive dependency: transitive
description: description:
name: audio_session name: audio_session
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.0" version: "0.1.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -64,6 +64,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0" version: "1.15.0"
collision:
dependency: transitive
description:
name: collision
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.3"
convert: convert:
dependency: "direct main" dependency: "direct main"
description: description:
@ -77,7 +84,7 @@ packages:
name: crop name: crop
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.1" version: "0.5.1+1"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
@ -154,7 +161,7 @@ packages:
name: ffi name: ffi
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.1.1"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -168,7 +175,7 @@ packages:
name: file_picker name: file_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2+2"
file_selector: file_selector:
dependency: "direct main" dependency: "direct main"
description: description:
@ -181,7 +188,7 @@ packages:
description: description:
path: "plugins/file_selector/file_selector_linux" path: "plugins/file_selector/file_selector_linux"
ref: HEAD ref: HEAD
resolved-ref: f2d8aa3820fb87316516670bf4d51a74de8ac0dd resolved-ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
url: "git://github.com/google/flutter-desktop-embedding.git" url: "git://github.com/google/flutter-desktop-embedding.git"
source: git source: git
version: "0.0.2" version: "0.0.2"
@ -190,7 +197,7 @@ packages:
description: description:
path: "plugins/file_selector/file_selector_macos" path: "plugins/file_selector/file_selector_macos"
ref: HEAD ref: HEAD
resolved-ref: f2d8aa3820fb87316516670bf4d51a74de8ac0dd resolved-ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
url: "git://github.com/google/flutter-desktop-embedding.git" url: "git://github.com/google/flutter-desktop-embedding.git"
source: git source: git
version: "0.0.4" version: "0.0.4"
@ -213,7 +220,7 @@ packages:
description: description:
path: "plugins/file_selector/file_selector_windows" path: "plugins/file_selector/file_selector_windows"
ref: HEAD ref: HEAD
resolved-ref: f2d8aa3820fb87316516670bf4d51a74de8ac0dd resolved-ref: e48abe7c3e9ebfe0b81622167c5201d4e783bb81
url: "git://github.com/google/flutter-desktop-embedding.git" url: "git://github.com/google/flutter-desktop-embedding.git"
source: git source: git
version: "0.0.2" version: "0.0.2"
@ -245,14 +252,14 @@ packages:
name: flutter_localized_locales name: flutter_localized_locales
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -302,7 +309,7 @@ packages:
name: image_picker name: image_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.5+2" version: "0.7.5+3"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
@ -342,7 +349,7 @@ packages:
name: just_audio name: just_audio
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.4+1" version: "0.7.5"
just_audio_platform_interface: just_audio_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -370,7 +377,7 @@ packages:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.4.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -398,7 +405,7 @@ packages:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -524,14 +531,14 @@ packages:
name: rxdart name: rxdart
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.26.0" version: "0.27.1"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.5" version: "2.0.6"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@ -620,7 +627,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.0" version: "0.4.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:

3
src/apps/chat/layer.rs

@ -445,6 +445,7 @@ impl LayerEvent {
// TODO // TODO
(NetworkMessage::Video, content) (NetworkMessage::Video, content)
} }
MessageType::Invite => (NetworkMessage::Invite(content.clone()), content),
}; };
let mut msg = Message::new(&mgid, fid, true, m_type, raw, false); let mut msg = Message::new(&mgid, fid, true, m_type, raw, false);
@ -499,7 +500,7 @@ pub(super) fn reject_message(
SendType::Event(uid, addr, data) SendType::Event(uid, addr, data)
} }
pub(super) fn event_message( pub(crate) fn event_message(
layer: &mut Layer, layer: &mut Layer,
tid: i64, tid: i64,
me_id: GroupId, me_id: GroupId,

2
src/apps/chat/mod.rs

@ -2,8 +2,8 @@ mod layer;
mod models; mod models;
pub(crate) mod rpc; pub(crate) mod rpc;
pub(crate) use layer::chat_conn;
pub(crate) use layer::handle as layer_handle; pub(crate) use layer::handle as layer_handle;
pub(crate) use layer::LayerEvent; pub(crate) use layer::LayerEvent;
pub(crate) use layer::{chat_conn, event_message};
pub(crate) use models::{Friend, Message, MessageType, NetworkMessage, Request}; pub(crate) use models::{Friend, Message, MessageType, NetworkMessage, Request};
pub(crate) use rpc::new_rpc_handler; pub(crate) use rpc::new_rpc_handler;

6
src/apps/chat/models.rs

@ -51,6 +51,7 @@ pub(crate) enum NetworkMessage {
Emoji, Emoji,
Phone, Phone,
Video, Video,
Invite(String),
None, None,
} }
@ -97,6 +98,7 @@ impl NetworkMessage {
// TODO // TODO
(MessageType::Video, "".to_owned()) (MessageType::Video, "".to_owned())
} }
NetworkMessage::Invite(content) => (MessageType::Invite, content),
NetworkMessage::None => { NetworkMessage::None => {
return Ok(Message::new_with_id( return Ok(Message::new_with_id(
hash, hash,
@ -148,6 +150,7 @@ impl NetworkMessage {
}; };
Ok(NetworkMessage::Record(bytes, time)) Ok(NetworkMessage::Record(bytes, time))
} }
MessageType::Invite => Ok(NetworkMessage::Invite(model.content)),
MessageType::Emoji => Ok(NetworkMessage::Emoji), MessageType::Emoji => Ok(NetworkMessage::Emoji),
MessageType::Phone => Ok(NetworkMessage::Phone), MessageType::Phone => Ok(NetworkMessage::Phone),
MessageType::Video => Ok(NetworkMessage::Video), MessageType::Video => Ok(NetworkMessage::Video),
@ -165,6 +168,7 @@ pub(crate) enum MessageType {
Record, Record,
Phone, Phone,
Video, Video,
Invite,
} }
impl MessageType { impl MessageType {
@ -178,6 +182,7 @@ impl MessageType {
MessageType::Record => 5, MessageType::Record => 5,
MessageType::Phone => 6, MessageType::Phone => 6,
MessageType::Video => 7, MessageType::Video => 7,
MessageType::Invite => 8,
} }
} }
@ -191,6 +196,7 @@ impl MessageType {
5 => MessageType::Record, 5 => MessageType::Record,
6 => MessageType::Phone, 6 => MessageType::Phone,
7 => MessageType::Video, 7 => MessageType::Video,
8 => MessageType::Invite,
_ => MessageType::String, _ => MessageType::String,
} }
} }

2
src/apps/group_chat/models.rs

@ -65,7 +65,7 @@ pub(crate) struct GroupChat {
/// group chat id. /// group chat id.
pub g_id: GroupId, pub g_id: GroupId,
/// group chat type. /// group chat type.
g_type: GroupType, pub g_type: GroupType,
/// group chat server addresse. /// group chat server addresse.
pub g_addr: PeerAddr, pub g_addr: PeerAddr,
/// group chat name. /// group chat name.

86
src/apps/group_chat/rpc.rs

@ -9,9 +9,9 @@ use tdn_did::Proof;
use group_chat_types::{CheckType, Event, GroupType, JoinProof, LayerEvent}; use group_chat_types::{CheckType, Event, GroupType, JoinProof, LayerEvent};
use crate::apps::chat::MessageType; use crate::apps::chat::{Friend, MessageType};
use crate::rpc::RpcState; use crate::rpc::RpcState;
use crate::storage::group_chat_db; use crate::storage::{chat_db, group_chat_db};
use super::add_layer; use super::add_layer;
use super::models::{to_network_message, GroupChat, GroupChatKey, Member, Message, Request}; use super::models::{to_network_message, GroupChat, GroupChatKey, Member, Message, Request};
@ -236,11 +236,14 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
handler.add_method( handler.add_method(
"group-chat-join", "group-chat-join",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move { |gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let gcd = GroupId::from_hex(params[0].as_str()?)?; let _gtype = GroupType::from_u32(params[0].as_i64()? as u32);
let gaddr = PeerAddr::from_hex(params[1].as_str()?)?; let gcd = GroupId::from_hex(params[1].as_str()?)?;
let gname = params[2].as_str()?.to_owned(); let gaddr = PeerAddr::from_hex(params[2].as_str()?)?;
let gremark = params[3].as_str()?.to_owned(); let gname = params[3].as_str()?.to_owned();
let gkey = params[4].as_str()?; let gremark = params[4].as_str()?.to_owned();
let gproof = params[5].as_str()?;
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 key = GroupChatKey::from_hex(gkey).unwrap_or(GroupChatKey::new(vec![]));
let mut request = Request::new_by_me(gcd, gaddr, gname, gremark, key); let mut request = Request::new_by_me(gcd, gaddr, gname, gremark, key);
@ -259,6 +262,75 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
}, },
); );
handler.add_method(
"group-chat-invite",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let id = params[0].as_i64()?;
let gcd = GroupId::from_hex(params[1].as_str()?)?;
let ids: Vec<i64> = params[2]
.as_array()?
.iter()
.filter_map(|v| v.as_i64())
.collect();
//
let group_lock = state.group.read().await;
let base = group_lock.base().clone();
let chat = chat_db(&base, &gid)?;
let group_db = group_chat_db(&base, &gid)?;
let mut invites = vec![];
for id in ids {
let friend = Friend::get_id(&chat, id)??;
if Member::get_id(&group_db, &id, &friend.gid).is_err() {
let proof = group_lock.prove_addr(&gid, &friend.gid.into())?;
invites.push((friend.id, friend.gid, friend.addr, proof));
}
}
drop(chat);
let gc = GroupChat::get_id(&group_db, &id)??;
let tmp_name = gc.g_name.replace(";", "-;");
let mut results = HandleResult::new();
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(),
);
// check if encrypted group type. need online.
if gc.g_type == GroupType::Encrypted {
if let Ok(addr) = layer_lock.running(&gid)?.online(&fgid) {
faddr = addr;
} else {
continue;
}
}
let (msg, nw) = crate::apps::chat::LayerEvent::from_message(
&base,
gid,
fid,
MessageType::Invite,
contact_values,
)
.await?;
let event = crate::apps::chat::LayerEvent::Message(msg.hash, nw);
let s =
crate::apps::chat::event_message(&mut layer_lock, msg.id, gid, faddr, &event);
results.layers.push((gid, fgid, s));
}
Ok(results)
},
);
handler.add_method( handler.add_method(
"group-chat-request-handle", "group-chat-request-handle",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move { |gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {

Loading…
Cancel
Save