Move Attribute Updates / File Downloading / Contact Requests / Invites to new API
continuous-integration/drone/pr Build is pending Details

This commit is contained in:
Sarah Jamie Lewis 2021-12-01 04:17:48 -08:00
parent b0f74ffb6d
commit d6839c62e3
10 changed files with 87 additions and 66 deletions

View File

@ -73,8 +73,9 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names
void SetProfileAttribute(String profile, String key, String val);
// ignore: non_constant_identifier_names
void SetConversationAttribute(String profile, int contact, String key, String val);
void SetConversationAttribute(String profile, int conversation, String key, String val);
// ignore: non_constant_identifier_names
void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val);
// ignore: non_constant_identifier_names
void LoadServers(String password);
// ignore: non_constant_identifier_names

View File

@ -79,7 +79,6 @@ class CwtchNotifier {
}
break;
case "GroupCreated":
// Retrieve Server Status from Cache...
String status = "";
RemoteServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
@ -131,19 +130,24 @@ class CwtchNotifier {
case "NewMessageFromPeer":
notificationManager.notify("New Message From Peer!");
var identifier = int.parse(data["ConversationID"]);
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.unreadMessages++;
} else {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++;
}
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages++;
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.now());
// We only ever see messages from authenticated peers.
// If the contact is marked as offline then override this - can happen when the contact is removed from the front
// end during syncing.
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.isOnline() == false) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.status = "Authenticated";
// We might not have received a contact created for this contact yet...
// In that case the **next** event we receive will actually update these values...
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier) != null) {
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.unreadMessages++;
} else {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++;
}
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages++;
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.now());
// We only ever see messages from authenticated peers.
// If the contact is marked as offline then override this - can happen when the contact is removed from the front
// end during syncing.
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.isOnline() == false) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.status = "Authenticated";
}
}
break;
@ -206,17 +210,7 @@ class CwtchNotifier {
notificationManager.notify("New Message From Group!");
}
} else {
// from me (already displayed - do not update counter)
var idx = data["Signature"];
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)?.getMessageKey(idx);
if (key == null) break;
try {
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
if (message == null) break;
message.ackd = true;
} catch (e) {
// ignore, we likely have an old key that has been replaced with an actual signature
}
// This is not dealt with by IndexedAcknowledgment
}
break;
case "MessageCounterResync":
@ -242,7 +236,7 @@ class CwtchNotifier {
break;
case "SendMessageToGroupError":
// from me (already displayed - do not update counter)
EnvironmentConfig.debugLog("SendMessageToGroupError");
EnvironmentConfig.debugLog("SendMessageToGroupError: $data");
var idx = data["Signature"];
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["GroupID"])!.getMessageKey(idx);
if (key == null) break;
@ -289,7 +283,6 @@ class CwtchNotifier {
profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]);
break;
case "NewGroup":
EnvironmentConfig.debugLog("new group");
String invite = data["GroupInvite"].toString();
if (invite.startsWith("torv3")) {
String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5)));
@ -303,7 +296,8 @@ class CwtchNotifier {
}
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["ConversationID"], groupInvite["GroupID"],
var identifier = int.parse(data["ConversationID"]);
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], identifier, groupInvite["GroupID"],
authorization: ContactAuthorization.approved,
imagePath: data["PicturePath"],
nickname: groupInvite["GroupName"],
@ -311,7 +305,7 @@ class CwtchNotifier {
status: status,
isGroup: true,
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(0)));
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.fromMillisecondsSinceEpoch(0));
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.fromMillisecondsSinceEpoch(0));
}
}
break;
@ -323,7 +317,7 @@ class CwtchNotifier {
break;
case "ServerStateChange":
// Update the Server Cache
EnvironmentConfig.debugLog("server state changes $data");
//EnvironmentConfig.debugLog("server state changes $data");
profileCN.getProfile(data["ProfileOnion"])?.updateServerStatusCache(data["GroupServer"], data["ConnectionState"]);
profileCN.getProfile(data["ProfileOnion"])?.contactList.contacts.forEach((contact) {
if (contact.isGroup == true && contact.server == data["GroupServer"]) {
@ -361,13 +355,17 @@ class CwtchNotifier {
}
break;
case "NewRetValMessageFromPeer":
if (data["Path"] == "name" && data["Data"].toString().trim().length > 0) {
// Update locally on the UI...
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]) != null) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.nickname = data["Data"];
if (data["Path"] == "profile.name") {
if (data["Data"].toString().trim().length > 0) {
// Update locally on the UI...
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]) != null) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"])!.nickname = data["Data"];
}
}
} else if (data['Path'] == "profile.picture") {
// Not yet..
} else {
EnvironmentConfig.debugLog("unhandled peer attribute event: ${data['Path']}");
EnvironmentConfig.debugLog("unhandled ret val event: ${data['Path']}");
}
break;
case "ManifestSaved":

View File

@ -84,6 +84,9 @@ typedef VoidFromStringIntStringFn = void Function(Pointer<Utf8>, int, int, Point
typedef void_from_string_int_string_string_function = Void Function(Pointer<Utf8>, Int32, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringIntStringStringFn = void Function(Pointer<Utf8>, int, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef void_from_string_int_int_int_string_string_function = Void Function(Pointer<Utf8>, Int32, Int32, Int32, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringIntIntIntStringStringFn = void Function(Pointer<Utf8>, int, int, int, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef void_from_string_int_int_function = Void Function(Pointer<Utf8>, Int32, Int32, Int32);
typedef VoidFromStringIntIntFn = void Function(Pointer<Utf8>, int, int, int);
@ -549,6 +552,21 @@ class CwtchFfi implements Cwtch {
malloc.free(u4);
}
@override
// ignore: non_constant_identifier_names
void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val) {
var setMessageAttribute = library.lookup<NativeFunction<void_from_string_int_int_int_string_string_function>>("c_SetMessageAttribute");
// ignore: non_constant_identifier_names
final SetMessageAttribute = setMessageAttribute.asFunction<VoidFromStringIntIntIntStringStringFn>();
final u1 = profile.toNativeUtf8();
final u3 = key.toNativeUtf8();
final u4 = val.toNativeUtf8();
SetMessageAttribute(u1, u1.length, conversation, channel, message, u3, u3.length, u4, u4.length);
malloc.free(u1);
malloc.free(u3);
malloc.free(u4);
}
@override
// ignore: non_constant_identifier_names
void LoadServers(String password) {

View File

@ -281,4 +281,9 @@ class CwtchGomobile implements Cwtch {
Future GetMessageByContentHash(String profile, int handle, String contentHash) {
return cwtchPlatform.invokeMethod("GetMessageByContentHash", {"profile": profile, "contact": handle, "contentHash": contentHash});
}
@override
void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val) {
cwtchPlatform.invokeMethod("SetMessageAttribute", {"ProfileOnion": profile, "Conversation": conversation, "Channel": channel, "Message": message, "Key": key, "Val": val});
}
}

View File

@ -43,7 +43,7 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, int co
}
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", null, 0, false, true);
var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", "", <String, String>{}, false, true);
try {
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
// There are 2 conditions in which this error condition can be met:
@ -67,11 +67,11 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, int co
var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
var senderHandle = messageWrapper['PeerID'];
var senderImage = messageWrapper['ContactImage'];
var flags = int.parse(messageWrapper['Flags'].toString());
var attributes = messageWrapper['Attributes'];
var ackd = messageWrapper['Acknowledged'];
var error = messageWrapper['Error'] != null;
var signature = messageWrapper['Signature'];
metadata = MessageMetadata(profileOnion, conversationIdentifier, index, messageID, timestamp, senderHandle, senderImage, signature, flags, ackd, error);
metadata = MessageMetadata(profileOnion, conversationIdentifier, index, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error);
dynamic message = jsonDecode(messageWrapper['Message']);
var content = message['d'] as dynamic;
@ -97,7 +97,7 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, int co
}
});
} catch (e) {
return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", null, 0, false, true)));
return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", "", <String, String>{}, false, true)));
}
}
@ -111,17 +111,13 @@ class MessageMetadata extends ChangeNotifier {
final DateTime timestamp;
final String senderHandle;
final String? senderImage;
int _flags;
final dynamic _attributes;
bool _ackd;
bool _error;
final String? signature;
int get flags => this._flags;
set flags(int newVal) {
this._flags = newVal;
notifyListeners();
}
dynamic get attributes => this._attributes;
bool get ackd => this._ackd;
set ackd(bool newVal) {
@ -135,6 +131,6 @@ class MessageMetadata extends ChangeNotifier {
notifyListeners();
}
MessageMetadata(
this.profileOnion, this.conversationIdentifier, this.messageIndex, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._flags, this._ackd, this._error);
MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageIndex, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd,
this._error);
}

View File

@ -195,10 +195,11 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
child: Text(AppLocalizations.of(context)!.yesLeave),
onPressed: () {
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
var handle = Provider.of<ContactInfoState>(context, listen: false).identifier;
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
// locally update cache...
Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, handle);
Provider.of<ProfileInfoState>(context, listen: false).contactList.removeContact(identifier);
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, identifier);
Future.delayed(Duration(milliseconds: 500), () {
Provider.of<AppState>(context, listen: false).selectedConversation = null;
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog

View File

@ -353,7 +353,7 @@ class _MessageViewState extends State<MessageView> {
return contact.onion != Provider.of<ContactInfoState>(context).onion;
}, onChanged: (newVal) {
setState(() {
this.selectedContact = newVal;
this.selectedContact = Provider.of<ProfileInfoState>(context).contactList.findContact(newVal)!.identifier;
});
})),
SizedBox(
@ -362,7 +362,7 @@ class _MessageViewState extends State<MessageView> {
ElevatedButton(
child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn),
onPressed: () {
if (this.selectedContact != "") {
if (this.selectedContact != -1) {
this._sendInvitation();
}
Navigator.pop(bcontext);

View File

@ -111,6 +111,8 @@ class _ContactRowState extends State<ContactRow> {
}
void _btnApprove() {
// Update the UI
Provider.of<ContactInfoState>(context, listen: false).authorization = ContactAuthorization.approved;
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.AcceptContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier);

View File

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:cwtch/config.dart';
import 'package:cwtch/models/message.dart';
import 'package:file_picker_desktop/file_picker_desktop.dart';
import 'package:flutter/cupertino.dart';
@ -41,7 +42,7 @@ class FileBubbleState extends State<FileBubble> {
@override
Widget build(BuildContext context) {
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
var flagStarted = Provider.of<MessageMetadata>(context).flags & 0x02 > 0;
var flagStarted = Provider.of<MessageMetadata>(context).attributes["file-downloaded"] == "true";
var borderRadiousEh = 15.0;
var showFileSharing = Provider.of<Settings>(context).isExperimentEnabled(FileSharingExperiment);
var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
@ -146,14 +147,13 @@ class FileBubbleState extends State<FileBubble> {
String? selectedFileName;
File? file;
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
var handle = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
var idx = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
var conversation = Provider.of<ContactInfoState>(context, listen: false).identifier;
var idx = Provider.of<MessageMetadata>(context, listen: false).messageID;
if (Platform.isAndroid) {
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
//Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageMetadata>(context, listen: false).flags | 0x02);
Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true");
//Provider.of<MessageMetadata>(context, listen: false).attributes |= 0x02;
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
if (contact != null) {
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateDownloadableFile(profileOnion, contact.identifier, widget.nameSuggestion, widget.fileKey());
@ -165,11 +165,11 @@ class FileBubbleState extends State<FileBubble> {
);
if (selectedFileName != null) {
file = File(selectedFileName);
print("saving to " + file.path);
EnvironmentConfig.debugLog("saving to " + file.path);
var manifestPath = file.path + ".manifest";
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
//Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageMetadata>(context, listen: false).flags | 0x02);
Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true");
//Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
if (contact != null) {
Provider.of<FlwtchState>(context, listen: false).cwtch.DownloadFile(profileOnion, contact.identifier, file.path, manifestPath, widget.fileKey());

View File

@ -39,7 +39,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
isAccepted = Provider.of<ProfileInfoState>(context).contactList.findContact(widget.inviteTarget) != null;
var borderRadiousEh = 15.0;
var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);
rejected = Provider.of<MessageMetadata>(context).flags & 0x01 == 0x01;
rejected = Provider.of<MessageMetadata>(context).attributes["rejected-invite"] == "true";
var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
// If the sender is not us, then we want to give them a nickname...
@ -128,10 +128,10 @@ class InvitationBubbleState extends State<InvitationBubble> {
void _btnReject() {
setState(() {
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
var idx = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
//Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageMetadata>(context, listen: false).flags | 0x01);
Provider.of<MessageMetadata>(context, listen: false).flags |= 0x01;
var conversation = Provider.of<ContactInfoState>(context, listen: false).identifier;
var idx = Provider.of<MessageMetadata>(context, listen: false).messageID;
Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "rejected-invite", "true");
//Provider.of<MessageMetadata>(context, listen: false).flags |= 0x01;
});
}