New Cwtch Library Integration #258
|
@ -1 +1 @@
|
|||
2021-11-09-18-25-v1.4.2
|
||||
2021-12-08-00-32-v1.5.0-7-g28a13aa
|
|
@ -1 +1 @@
|
|||
2021-11-09-23-25-v1.4.2
|
||||
2021-12-08-05-32-v1.5.0-7-g28a13aa
|
|
@ -133,10 +133,10 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
notificationManager.notify(dlID, newNotification);
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.i("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace());
|
||||
Log.d("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace());
|
||||
}
|
||||
} else if (evt.EventType == "FileDownloaded") {
|
||||
Log.i("FlwtchWorker", "file downloaded!");
|
||||
Log.d("FlwtchWorker", "file downloaded!");
|
||||
val data = JSONObject(evt.Data);
|
||||
val tempFile = data.getString("TempFile");
|
||||
val fileKey = data.getString("FileKey");
|
||||
|
@ -147,7 +147,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
val targetUri = Uri.parse(filePath);
|
||||
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri);
|
||||
val bytesWritten = Files.copy(sourcePath, os);
|
||||
Log.i("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes");
|
||||
Log.d("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes");
|
||||
if (bytesWritten != 0L) {
|
||||
os?.flush();
|
||||
os?.close();
|
||||
|
@ -181,61 +181,70 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
Cwtch.loadProfiles(pass)
|
||||
}
|
||||
"GetMessage" -> {
|
||||
val profile = (a.get("profile") as? String) ?: ""
|
||||
val handle = (a.get("contact") as? String) ?: ""
|
||||
val indexI = a.getInt("index")
|
||||
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build())
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
val indexI = a.getInt("index").toLong()
|
||||
Log.d("FlwtchWorker", "Cwtch GetMessage " + profile + " " + conversation.toString() + " " + indexI.toString())
|
||||
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, conversation, indexI)).build())
|
||||
}
|
||||
"GetMessageByID" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
val id = a.getInt("id").toLong()
|
||||
return Result.success(Data.Builder().putString("result", Cwtch.getMessageByID(profile, conversation, id)).build())
|
||||
}
|
||||
"GetMessageByContentHash" -> {
|
||||
val profile = (a.get("profile") as? String) ?: ""
|
||||
val handle = (a.get("contact") as? String) ?: ""
|
||||
val contentHash = (a.get("contentHash") as? String) ?: ""
|
||||
return Result.success(Data.Builder().putString("result", Cwtch.getMessagesByContentHash(profile, handle, contentHash)).build())
|
||||
}
|
||||
"UpdateMessageFlags" -> {
|
||||
val profile = (a.get("profile") as? String) ?: ""
|
||||
val handle = (a.get("contact") as? String) ?: ""
|
||||
val midx = (a.get("midx") as? Long) ?: 0
|
||||
val flags = (a.get("flags") as? Long) ?: 0
|
||||
Cwtch.updateMessageFlags(profile, handle, midx, flags)
|
||||
}
|
||||
"AcceptContact" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
Cwtch.acceptContact(profile, handle)
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
val contentHash = (a.get("contentHash") as? String) ?: ""
|
||||
return Result.success(Data.Builder().putString("result", Cwtch.getMessagesByContentHash(profile, conversation, contentHash)).build())
|
||||
}
|
||||
"UpdateMessageAttribute" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
val channel = a.getInt("chanenl").toLong()
|
||||
val midx = a.getInt("midx").toLong()
|
||||
val key = (a.get("key") as? String) ?: ""
|
||||
val value = (a.get("value") as? String) ?: ""
|
||||
Cwtch.setMessageAttribute(profile, conversation, channel, midx, key, value)
|
||||
}
|
||||
"AcceptConversation" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
Cwtch.acceptConversation(profile, conversation)
|
||||
}
|
||||
"BlockContact" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
Cwtch.blockContact(profile, handle)
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
Cwtch.blockContact(profile, conversation)
|
||||
}
|
||||
"SendMessage" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
val message = (a.get("message") as? String) ?: ""
|
||||
Cwtch.sendMessage(profile, handle, message)
|
||||
Cwtch.sendMessage(profile, conversation, message)
|
||||
}
|
||||
"SendInvitation" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val target = (a.get("target") as? String) ?: ""
|
||||
Cwtch.sendInvitation(profile, handle, target)
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
val target = (a.get("target") as? Long) ?: -1
|
||||
Cwtch.sendInvitation(profile, conversation, target)
|
||||
}
|
||||
"ShareFile" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
val filepath = (a.get("filepath") as? String) ?: ""
|
||||
Cwtch.shareFile(profile, handle, filepath)
|
||||
Cwtch.shareFile(profile, conversation, filepath)
|
||||
}
|
||||
"DownloadFile" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
val filepath = (a.get("filepath") as? String) ?: ""
|
||||
val manifestpath = (a.get("manifestpath") as? String) ?: ""
|
||||
val filekey = (a.get("filekey") as? String) ?: ""
|
||||
// FIXME: Prevent spurious calls by Intent
|
||||
if (profile != "") {
|
||||
Cwtch.downloadFile(profile, handle, filepath, manifestpath, filekey)
|
||||
Cwtch.downloadFile(profile, conversation, filepath, manifestpath, filekey)
|
||||
}
|
||||
}
|
||||
"CheckDownloadStatus" -> {
|
||||
|
@ -245,9 +254,9 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
}
|
||||
"VerifyOrResumeDownload" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val conversation = a.getInt("conversation").toLong()
|
||||
val fileKey = (a.get("fileKey") as? String) ?: ""
|
||||
Cwtch.verifyOrResumeDownload(profile, handle, fileKey)
|
||||
Cwtch.verifyOrResumeDownload(profile, conversation, fileKey)
|
||||
}
|
||||
"SendProfileEvent" -> {
|
||||
val onion = (a.get("onion") as? String) ?: ""
|
||||
|
@ -266,13 +275,6 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
val bundle = (a.get("bundle") as? String) ?: ""
|
||||
Cwtch.importBundle(profile, bundle)
|
||||
}
|
||||
"SetGroupAttribute" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val groupHandle = (a.get("groupHandle") as? String) ?: ""
|
||||
val key = (a.get("key") as? String) ?: ""
|
||||
val value = (a.get("value") as? String) ?: ""
|
||||
Cwtch.setGroupAttribute(profile, groupHandle, key, value)
|
||||
}
|
||||
"CreateGroup" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val server = (a.get("server") as? String) ?: ""
|
||||
|
@ -286,18 +288,13 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
}
|
||||
"ArchiveConversation" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val contactHandle = (a.get("handle") as? String) ?: ""
|
||||
Cwtch.archiveConversation(profile, contactHandle)
|
||||
val conversation = (a.get("conversation") as? Long) ?: -1
|
||||
Cwtch.archiveConversation(profile, conversation)
|
||||
}
|
||||
"DeleteContact" -> {
|
||||
"DeleteConversation" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
Cwtch.deleteContact(profile, handle)
|
||||
}
|
||||
"RejectInvite" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val groupHandle = (a.get("groupHandle") as? String) ?: ""
|
||||
Cwtch.rejectInvite(profile, groupHandle)
|
||||
val conversation = (a.get("conversation") as? Long) ?: -1
|
||||
Cwtch.deleteContact(profile, conversation)
|
||||
}
|
||||
"SetProfileAttribute" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
|
@ -305,12 +302,12 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
val v = (a.get("Val") as? String) ?: ""
|
||||
Cwtch.setProfileAttribute(profile, key, v)
|
||||
}
|
||||
"SetContactAttribute" -> {
|
||||
"SetConversationAttribute" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val contact = (a.get("Contact") as? String) ?: ""
|
||||
val conversation = (a.get("conversation") as? Long) ?: -1
|
||||
val key = (a.get("Key") as? String) ?: ""
|
||||
val v = (a.get("Val") as? String) ?: ""
|
||||
Cwtch.setContactAttribute(profile, contact, key, v)
|
||||
Cwtch.setConversationAttribute(profile, conversation, key, v)
|
||||
}
|
||||
"Shutdown" -> {
|
||||
Cwtch.shutdownCwtch();
|
||||
|
@ -354,7 +351,10 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
val v = (a.get("Val") as? String) ?: ""
|
||||
Cwtch.setServerAttribute(serverOnion, key, v)
|
||||
}
|
||||
else -> return Result.failure()
|
||||
else -> {
|
||||
Log.i("FlwtchWorker", "unknown command: " + method);
|
||||
return Result.failure()
|
||||
}
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
|
|
@ -147,11 +147,7 @@ class MainActivity: FlutterActivity() {
|
|||
// that we can divert this method call to ReconnectCwtchForeground instead if so.
|
||||
val works = WorkManager.getInstance(this).getWorkInfosByTag(WORKER_TAG).get()
|
||||
for (workInfo in works) {
|
||||
Log.i("handleCwtch:WorkManager", "$workInfo")
|
||||
if (!workInfo.tags.contains(uniqueTag)) {
|
||||
Log.i("handleCwtch:WorkManager", "canceling ${workInfo.id} bc tags don't include $uniqueTag")
|
||||
WorkManager.getInstance(this).cancelWorkById(workInfo.id)
|
||||
}
|
||||
WorkManager.getInstance(this).cancelWorkById(workInfo.id)
|
||||
}
|
||||
WorkManager.getInstance(this).pruneWork()
|
||||
|
||||
|
|
|
@ -10,8 +10,6 @@ abstract class Cwtch {
|
|||
// ignore: non_constant_identifier_names
|
||||
Future<void> ReconnectCwtchForeground();
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void SelectProfile(String onion);
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateProfile(String nick, String pass);
|
||||
// ignore: non_constant_identifier_names
|
||||
|
@ -29,36 +27,39 @@ abstract class Cwtch {
|
|||
void SendAppEvent(String jsonEvent);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void AcceptContact(String profileOnion, String contactHandle);
|
||||
void AcceptContact(String profileOnion, int contactHandle);
|
||||
// ignore: non_constant_identifier_names
|
||||
void BlockContact(String profileOnion, String contactHandle);
|
||||
void BlockContact(String profileOnion, int contactHandle);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<dynamic> GetMessage(String profile, String handle, int index);
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<dynamic> GetMessageByContentHash(String profile, String handle, String contentHash);
|
||||
// ignore: non_constant_identifier_names
|
||||
void UpdateMessageFlags(String profile, String handle, int index, int flags);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendMessage(String profile, String handle, String message);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendInvitation(String profile, String handle, String target);
|
||||
Future<dynamic> GetMessage(String profile, int handle, int index);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ShareFile(String profile, String handle, String filepath);
|
||||
Future<dynamic> GetMessageByID(String profile, int handle, int index);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void DownloadFile(String profile, String handle, String filepath, String manifestpath, String filekey);
|
||||
Future<dynamic> GetMessageByContentHash(String profile, int handle, String contentHash);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateDownloadableFile(String profile, String handle, String filenameSuggestion, String filekey);
|
||||
void SendMessage(String profile, int handle, String message);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendInvitation(String profile, int handle, int target);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ShareFile(String profile, int handle, String filepath);
|
||||
// ignore: non_constant_identifier_names
|
||||
void DownloadFile(String profile, int handle, String filepath, String manifestpath, String filekey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateDownloadableFile(String profile, int handle, String filenameSuggestion, String filekey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void CheckDownloadStatus(String profile, String fileKey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profile, String handle, String filekey);
|
||||
void VerifyOrResumeDownload(String profile, int handle, String filekey);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ArchiveConversation(String profile, String handle);
|
||||
void ArchiveConversation(String profile, int handle);
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteContact(String profile, String handle);
|
||||
void DeleteContact(String profile, int handle);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateGroup(String profile, String server, String groupName);
|
||||
|
@ -66,14 +67,11 @@ abstract class Cwtch {
|
|||
// ignore: non_constant_identifier_names
|
||||
void ImportBundle(String profile, String bundle);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetGroupAttribute(String profile, String groupHandle, String key, String value);
|
||||
// ignore: non_constant_identifier_names
|
||||
void RejectInvite(String profileOnion, String groupHandle);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetProfileAttribute(String profile, String key, String val);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetContactAttribute(String profile, String 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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cwtch/main.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/models/profileservers.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
|
@ -23,7 +24,8 @@ class CwtchNotifier {
|
|||
late AppState appState;
|
||||
late ServerListState serverListState;
|
||||
|
||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) {
|
||||
CwtchNotifier(
|
||||
ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) {
|
||||
profileCN = pcn;
|
||||
settings = settingsCN;
|
||||
error = errorCN;
|
||||
|
@ -34,6 +36,7 @@ class CwtchNotifier {
|
|||
}
|
||||
|
||||
void handleMessage(String type, dynamic data) {
|
||||
//EnvironmentConfig.debugLog("NewEvent $type $data");
|
||||
sarah marked this conversation as resolved
|
||||
switch (type) {
|
||||
case "CwtchStarted":
|
||||
appState.SetCwtchInit();
|
||||
|
@ -42,12 +45,15 @@ class CwtchNotifier {
|
|||
appState.SetAppError(data["Error"]);
|
||||
break;
|
||||
case "NewPeer":
|
||||
EnvironmentConfig.debugLog("NewPeer $data");
|
||||
// if tag != v1-defaultPassword then it is either encrypted OR it is an unencrypted account created during pre-beta...
|
||||
profileCN.add(data["Identity"], data["name"], data["picture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true", data["tag"] != "v1-defaultPassword");
|
||||
break;
|
||||
case "PeerCreated":
|
||||
case "ContactCreated":
|
||||
EnvironmentConfig.debugLog("NewServer $data");
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(
|
||||
data["ProfileOnion"],
|
||||
int.parse(data["ConversationID"]),
|
||||
data["RemotePeer"],
|
||||
nickname: data["nick"],
|
||||
status: data["status"],
|
||||
|
@ -64,13 +70,7 @@ class CwtchNotifier {
|
|||
break;
|
||||
case "NewServer":
|
||||
EnvironmentConfig.debugLog("NewServer $data");
|
||||
serverListState.add(
|
||||
data["Onion"],
|
||||
data["ServerBundle"],
|
||||
data["Running"] == "true",
|
||||
data["Description"],
|
||||
data["Autostart"] == "true",
|
||||
data["StorageType"] == "storage-password");
|
||||
serverListState.add(data["Onion"], data["ServerBundle"], data["Running"] == "true", data["Description"], data["Autostart"] == "true", data["StorageType"] == "storage-password");
|
||||
break;
|
||||
case "ServerIntentUpdate":
|
||||
EnvironmentConfig.debugLog("ServerIntentUpdate $data");
|
||||
|
@ -80,7 +80,6 @@ class CwtchNotifier {
|
|||
}
|
||||
break;
|
||||
case "GroupCreated":
|
||||
|
||||
// Retrieve Server Status from Cache...
|
||||
String status = "";
|
||||
RemoteServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
|
||||
|
@ -88,7 +87,7 @@ class CwtchNotifier {
|
|||
status = serverInfoState.status;
|
||||
}
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"],
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["ConversationID"], data["GroupID"],
|
||||
authorization: ContactAuthorization.approved,
|
||||
imagePath: data["PicturePath"],
|
||||
nickname: data["GroupName"],
|
||||
|
@ -111,13 +110,13 @@ class CwtchNotifier {
|
|||
}
|
||||
break;
|
||||
case "DeleteContact":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["RemotePeer"]);
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["ConversationID"]);
|
||||
break;
|
||||
case "DeleteGroup":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["GroupID"]);
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["ConversationID"]);
|
||||
break;
|
||||
case "PeerStateChange":
|
||||
ContactInfoState? contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]);
|
||||
ContactInfoState? contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
|
||||
if (contact != null) {
|
||||
if (data["ConnectionState"] != null) {
|
||||
contact.status = data["ConnectionState"];
|
||||
|
@ -131,19 +130,29 @@ class CwtchNotifier {
|
|||
break;
|
||||
case "NewMessageFromPeer":
|
||||
notificationManager.notify("New Message From Peer!");
|
||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["RemotePeer"]) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.unreadMessages++;
|
||||
} else {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.newMarker++;
|
||||
}
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
||||
var identifier = int.parse(data["ConversationID"]);
|
||||
var messageID = int.parse(data["Index"]);
|
||||
var timestamp = DateTime.tryParse(data['TimestampReceived'])!;
|
||||
var senderHandle = data['RemotePeer'];
|
||||
var senderImage = data['Picture'];
|
||||
|
||||
// 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(data["RemotePeer"])!.isOnline() == false) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.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.updateLastMessageTime(identifier, DateTime.now());
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, messageID, timestamp, senderHandle, senderImage, data["Data"]);
|
||||
|
||||
// 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;
|
||||
|
@ -151,43 +160,49 @@ class CwtchNotifier {
|
|||
// We don't use these anymore, IndexedAcknowledgement is more suited to the UI front end...
|
||||
break;
|
||||
case "IndexedAcknowledgement":
|
||||
var idx = data["Index"];
|
||||
var conversation = int.parse(data["ConversationID"]);
|
||||
var messageID = int.parse(data["Index"]);
|
||||
|
||||
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(conversation);
|
||||
// We return -1 for protocol message acks if there is no message
|
||||
if (idx == "-1") break;
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
||||
if (messageID == -1) break;
|
||||
var key = contact!.getMessageKeyOrFail(conversation, messageID);
|
||||
if (key == null) break;
|
||||
try {
|
||||
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||
if (message == null) break;
|
||||
message.ackd = true;
|
||||
|
||||
// We only ever see acks 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(data["RemotePeer"])!.isOnline() == false) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.status = "Authenticated";
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(conversation)!.isOnline() == false) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(conversation)!.status = "Authenticated";
|
||||
}
|
||||
message.ackd = true;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(conversation)!.ackCache(messageID);
|
||||
} catch (e) {
|
||||
// ignore, we received an ack for a message that hasn't loaded onto the screen yet...
|
||||
// the protocol was faster than the ui....yay?
|
||||
// ignore, most likely cause is the key got optimized out...
|
||||
}
|
||||
break;
|
||||
case "NewMessageFromGroup":
|
||||
var identifier = int.parse(data["ConversationID"]);
|
||||
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
||||
var idx = int.parse(data["Index"]);
|
||||
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages;
|
||||
var senderHandle = data['RemotePeer'];
|
||||
var senderImage = data['Picture'];
|
||||
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
||||
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages;
|
||||
|
||||
// Only bother to do anything if we know about the group and the provided index is greater than our current total...
|
||||
if (currentTotal != null && idx >= currentTotal) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages = idx + 1;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, idx, timestampSent, senderHandle, senderImage, data["Data"]);
|
||||
|
||||
//if not currently open
|
||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.unreadMessages++;
|
||||
} else {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.newMarker++;
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++;
|
||||
}
|
||||
|
||||
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
||||
// TODO: There are 2 timestamps associated with a new group message - time sent and time received.
|
||||
// Sent refers to the time a profile alleges they sent a message
|
||||
// Received refers to the time we actually saw the message from the server
|
||||
|
@ -198,56 +213,24 @@ class CwtchNotifier {
|
|||
// For now we perform some minimal checks on the sent timestamp to use to provide a useful ordering for honest contacts
|
||||
// and ensure that malicious contacts in groups can only set this timestamp to a value within the range of `last seen message time`
|
||||
// and `local now`.
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], timestampSent.toLocal());
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, timestampSent.toLocal());
|
||||
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(data["GroupID"])?.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
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "MessageCounterResync":
|
||||
var contactHandle = data["RemotePeer"];
|
||||
if (contactHandle == null || contactHandle == "") contactHandle = data["GroupID"];
|
||||
var total = int.parse(data["Data"]);
|
||||
if (total != profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages) {
|
||||
profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages = total;
|
||||
// This is dealt with by IndexedAcknowledgment
|
||||
EnvironmentConfig.debugLog("new message from group from yourself - this should not happen");
|
||||
}
|
||||
break;
|
||||
case "SendMessageToPeerError":
|
||||
// Ignore
|
||||
break;
|
||||
case "IndexedFailure":
|
||||
var idx = data["Index"];
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])?.getMessageKey(idx);
|
||||
try {
|
||||
var message = Provider.of<MessageMetadata>(key!.currentContext!, listen: false);
|
||||
message.error = true;
|
||||
} catch (e) {
|
||||
// ignore, we likely have an old key that has been replaced with an actual signature
|
||||
}
|
||||
break;
|
||||
case "SendMessageToGroupError":
|
||||
// from me (already displayed - do not update counter)
|
||||
EnvironmentConfig.debugLog("SendMessageToGroupError");
|
||||
var idx = data["Signature"];
|
||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
||||
if (key == null) break;
|
||||
try {
|
||||
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
|
||||
var idx = int.parse(data["Index"]);
|
||||
var key = contact?.getMessageKeyOrFail(contact.identifier, idx);
|
||||
if (key != null) {
|
||||
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||
if (message == null) break;
|
||||
message.error = true;
|
||||
} catch (e) {
|
||||
// ignore, we likely have an old key that has been replaced with an actual signature
|
||||
}
|
||||
break;
|
||||
case "AppError":
|
||||
|
@ -262,8 +245,8 @@ class CwtchNotifier {
|
|||
case "UpdateGlobalSettings":
|
||||
settings.handleUpdate(jsonDecode(data["Data"]));
|
||||
break;
|
||||
case "SetAttribute":
|
||||
if (data["Key"] == "public.name") {
|
||||
case "UpdatedProfileAttribute":
|
||||
if (data["Key"] == "public.profile.name") {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.nickname = data["Data"];
|
||||
} else {
|
||||
EnvironmentConfig.debugLog("unhandled set attribute event: ${data['Key']}");
|
||||
|
@ -285,7 +268,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)));
|
||||
|
@ -298,8 +280,9 @@ class CwtchNotifier {
|
|||
status = serverInfoState.status;
|
||||
}
|
||||
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) {
|
||||
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"],
|
||||
|
@ -307,7 +290,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;
|
||||
|
@ -319,7 +302,6 @@ class CwtchNotifier {
|
|||
break;
|
||||
case "ServerStateChange":
|
||||
// Update the Server Cache
|
||||
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"]) {
|
||||
|
@ -357,13 +339,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":
|
||||
|
@ -380,6 +366,8 @@ class CwtchNotifier {
|
|||
case "FileDownloaded":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkFinished(data["FileKey"], data["FilePath"]);
|
||||
break;
|
||||
case "ImportingProfileEvent":
|
||||
break;
|
||||
default:
|
||||
EnvironmentConfig.debugLog("unhandled event: $type");
|
||||
}
|
||||
|
|
|
@ -36,8 +36,9 @@ typedef VoidFromStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer
|
|||
typedef void_from_string_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef VoidFromStringStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef void_from_string_string_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef VoidFromStringStringStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
// DownloadFile
|
||||
typedef void_from_string_int_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef VoidFromStringIntStringStringStringFn = void Function(Pointer<Utf8>, int, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef void_from_string_string_int_int_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int64, Int64);
|
||||
typedef VoidFromStringStringIntIntFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
|
||||
|
@ -51,6 +52,9 @@ typedef StringFn = void Function(Pointer<Utf8> dir, int);
|
|||
typedef string_string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
||||
typedef StringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef string_int_to_void_function = Void Function(Pointer<Utf8> str, Int32 length, Int32 handle);
|
||||
typedef VoidFromStringIntFn = void Function(Pointer<Utf8>, int, int);
|
||||
|
||||
typedef get_json_blob_string_function = Pointer<Utf8> Function(Pointer<Utf8> str, Int32 length);
|
||||
typedef GetJsonBlobStringFn = Pointer<Utf8> Function(Pointer<Utf8> str, int len);
|
||||
|
||||
|
@ -58,10 +62,34 @@ typedef GetJsonBlobStringFn = Pointer<Utf8> Function(Pointer<Utf8> str, int len)
|
|||
typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32);
|
||||
typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int);
|
||||
|
||||
typedef get_json_blob_from_str_int_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Int32, Int32);
|
||||
typedef GetJsonBlobFromStrIntIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, int, int);
|
||||
|
||||
typedef get_json_blob_from_str_int_string_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Int32, Pointer<Utf8>, Int32);
|
||||
typedef GetJsonBlobFromStrIntStringFn = Pointer<Utf8> Function(
|
||||
Pointer<Utf8>,
|
||||
int,
|
||||
int,
|
||||
Pointer<Utf8>,
|
||||
int,
|
||||
);
|
||||
|
||||
// func c_GetMessagesByContentHash(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, contenthash_ptr *C.char, contenthash_len C.int) *C.char
|
||||
typedef get_json_blob_from_str_str_str_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef GetJsonBlobFromStrStrStrFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef void_from_string_int_string_function = Void Function(Pointer<Utf8>, Int32, Int32, Pointer<Utf8>, Int32);
|
||||
typedef VoidFromStringIntStringFn = void Function(Pointer<Utf8>, int, int, Pointer<Utf8>, int);
|
||||
|
||||
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);
|
||||
|
||||
typedef appbus_events_function = Pointer<Utf8> Function();
|
||||
typedef AppbusEventsFn = Pointer<Utf8> Function();
|
||||
|
||||
|
@ -233,16 +261,6 @@ class CwtchFfi implements Cwtch {
|
|||
}
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void SelectProfile(String onion) async {
|
||||
var selectProfileC = library.lookup<NativeFunction<get_json_blob_string_function>>("c_SelectProfile");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SelectProfile = selectProfileC.asFunction<GetJsonBlobStringFn>();
|
||||
final ut8Onion = onion.toNativeUtf8();
|
||||
SelectProfile(ut8Onion, ut8Onion.length);
|
||||
malloc.free(ut8Onion);
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateProfile(String nick, String pass) {
|
||||
var createProfileC = library.lookup<NativeFunction<void_from_string_string_function>>("c_CreateProfile");
|
||||
|
@ -266,17 +284,15 @@ class CwtchFfi implements Cwtch {
|
|||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> GetMessage(String profile, String handle, int index) async {
|
||||
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_str_int_function>>("c_GetMessage");
|
||||
Future<String> GetMessage(String profile, int handle, int index) async {
|
||||
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_GetMessage");
|
||||
// ignore: non_constant_identifier_names
|
||||
final GetMessage = getMessageC.asFunction<GetJsonBlobFromStrStrIntFn>();
|
||||
final GetMessage = getMessageC.asFunction<GetJsonBlobFromStrIntIntFn>();
|
||||
final utf8profile = profile.toNativeUtf8();
|
||||
final utf8handle = handle.toNativeUtf8();
|
||||
Pointer<Utf8> jsonMessageBytes = GetMessage(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index);
|
||||
Pointer<Utf8> jsonMessageBytes = GetMessage(utf8profile, utf8profile.length, handle, index);
|
||||
String jsonMessage = jsonMessageBytes.toDartString();
|
||||
_UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(jsonMessageBytes);
|
||||
malloc.free(utf8profile);
|
||||
malloc.free(utf8handle);
|
||||
return jsonMessage;
|
||||
}
|
||||
|
||||
|
@ -306,89 +322,75 @@ class CwtchFfi implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void AcceptContact(String profileOnion, String contactHandle) {
|
||||
var acceptContact = library.lookup<NativeFunction<string_string_to_void_function>>("c_AcceptContact");
|
||||
void AcceptContact(String profileOnion, int contactHandle) {
|
||||
var acceptContact = library.lookup<NativeFunction<string_int_to_void_function>>("c_AcceptConversation");
|
||||
// ignore: non_constant_identifier_names
|
||||
final AcceptContact = acceptContact.asFunction<VoidFromStringStringFn>();
|
||||
final AcceptContact = acceptContact.asFunction<VoidFromStringIntFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
AcceptContact(u1, u1.length, u2, u2.length);
|
||||
AcceptContact(u1, u1.length, contactHandle);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void BlockContact(String profileOnion, String contactHandle) {
|
||||
var blockContact = library.lookup<NativeFunction<string_string_to_void_function>>("c_BlockContact");
|
||||
void BlockContact(String profileOnion, int contactHandle) {
|
||||
var blockContact = library.lookup<NativeFunction<string_int_to_void_function>>("c_BlockContact");
|
||||
// ignore: non_constant_identifier_names
|
||||
final BlockContact = blockContact.asFunction<VoidFromStringStringFn>();
|
||||
final BlockContact = blockContact.asFunction<VoidFromStringIntFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
BlockContact(u1, u1.length, u2, u2.length);
|
||||
BlockContact(u1, u1.length, contactHandle);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendMessage(String profileOnion, String contactHandle, String message) {
|
||||
var sendMessage = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_SendMessage");
|
||||
void SendMessage(String profileOnion, int contactHandle, String message) {
|
||||
var sendMessage = library.lookup<NativeFunction<void_from_string_int_string_function>>("c_SendMessage");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SendMessage = sendMessage.asFunction<VoidFromStringStringStringFn>();
|
||||
final SendMessage = sendMessage.asFunction<VoidFromStringIntStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = message.toNativeUtf8();
|
||||
SendMessage(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
SendMessage(u1, u1.length, contactHandle, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendInvitation(String profileOnion, String contactHandle, String target) {
|
||||
var sendInvitation = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_SendInvitation");
|
||||
void SendInvitation(String profileOnion, int contactHandle, int target) {
|
||||
var sendInvitation = library.lookup<NativeFunction<void_from_string_int_int_function>>("c_SendInvitation");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SendInvitation = sendInvitation.asFunction<VoidFromStringStringStringFn>();
|
||||
final SendInvitation = sendInvitation.asFunction<VoidFromStringIntIntFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = target.toNativeUtf8();
|
||||
SendInvitation(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
SendInvitation(u1, u1.length, contactHandle, target);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ShareFile(String profileOnion, String contactHandle, String filepath) {
|
||||
var shareFile = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_ShareFile");
|
||||
void ShareFile(String profileOnion, int contactHandle, String filepath) {
|
||||
var shareFile = library.lookup<NativeFunction<void_from_string_int_string_function>>("c_ShareFile");
|
||||
// ignore: non_constant_identifier_names
|
||||
final ShareFile = shareFile.asFunction<VoidFromStringStringStringFn>();
|
||||
final ShareFile = shareFile.asFunction<VoidFromStringIntStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = filepath.toNativeUtf8();
|
||||
ShareFile(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
ShareFile(u1, u1.length, contactHandle, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DownloadFile(String profileOnion, String contactHandle, String filepath, String manifestpath, String filekey) {
|
||||
var dlFile = library.lookup<NativeFunction<void_from_string_string_string_string_string_function>>("c_DownloadFile");
|
||||
void DownloadFile(String profileOnion, int contactHandle, String filepath, String manifestpath, String filekey) {
|
||||
var dlFile = library.lookup<NativeFunction<void_from_string_int_string_string_string_function>>("c_DownloadFile");
|
||||
// ignore: non_constant_identifier_names
|
||||
final DownloadFile = dlFile.asFunction<VoidFromStringStringStringStringStringFn>();
|
||||
final DownloadFile = dlFile.asFunction<VoidFromStringIntStringStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = filepath.toNativeUtf8();
|
||||
final u4 = manifestpath.toNativeUtf8();
|
||||
final u5 = filekey.toNativeUtf8();
|
||||
DownloadFile(u1, u1.length, u2, u2.length, u3, u3.length, u4, u4.length, u5, u5.length);
|
||||
DownloadFile(u1, u1.length, contactHandle, u3, u3.length, u4, u4.length, u5, u5.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
malloc.free(u4);
|
||||
malloc.free(u5);
|
||||
|
@ -396,7 +398,7 @@ class CwtchFfi implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateDownloadableFile(String profileOnion, String contactHandle, String filenameSuggestion, String filekey) {
|
||||
void CreateDownloadableFile(String profileOnion, int contactHandle, String filenameSuggestion, String filekey) {
|
||||
// android only - do nothing
|
||||
}
|
||||
|
||||
|
@ -415,20 +417,17 @@ class CwtchFfi implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profileOnion, String contactHandle, String filekey) {
|
||||
var fn = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_VerifyOrResumeDownload");
|
||||
void VerifyOrResumeDownload(String profileOnion, int contactHandle, String filekey) {
|
||||
var fn = library.lookup<NativeFunction<void_from_string_int_string_function>>("c_VerifyOrResumeDownload");
|
||||
// ignore: non_constant_identifier_names
|
||||
final VerifyOrResumeDownload = fn.asFunction<VoidFromStringStringStringFn>();
|
||||
final VerifyOrResumeDownload = fn.asFunction<VoidFromStringIntStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = filekey.toNativeUtf8();
|
||||
VerifyOrResumeDownload(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
VerifyOrResumeDownload(u1, u1.length, contactHandle, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ResetTor() {
|
||||
|
@ -451,36 +450,6 @@ class CwtchFfi implements Cwtch {
|
|||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetGroupAttribute(String profileOnion, String groupHandle, String key, String value) {
|
||||
var setGroupAttribute = library.lookup<NativeFunction<void_from_string_string_string_string_function>>("c_SetGroupAttribute");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SetGroupAttribute = setGroupAttribute.asFunction<VoidFromStringStringStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = groupHandle.toNativeUtf8();
|
||||
final u3 = key.toNativeUtf8();
|
||||
final u4 = value.toNativeUtf8();
|
||||
SetGroupAttribute(u1, u1.length, u2, u2.length, u3, u3.length, u4, u4.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
malloc.free(u4);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void RejectInvite(String profileOnion, String groupHandle) {
|
||||
var rejectInvite = library.lookup<NativeFunction<string_string_to_void_function>>("c_RejectInvite");
|
||||
// ignore: non_constant_identifier_names
|
||||
final RejectInvite = rejectInvite.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = groupHandle.toNativeUtf8();
|
||||
RejectInvite(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateGroup(String profileOnion, String server, String groupName) {
|
||||
|
@ -499,41 +468,24 @@ class CwtchFfi implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ArchiveConversation(String profileOnion, String handle) {
|
||||
var archiveConversation = library.lookup<NativeFunction<string_string_to_void_function>>("c_ArchiveConversation");
|
||||
void ArchiveConversation(String profileOnion, int handle) {
|
||||
var archiveConversation = library.lookup<NativeFunction<string_int_to_void_function>>("c_ArchiveConversation");
|
||||
// ignore: non_constant_identifier_names
|
||||
final ArchiveConversation = archiveConversation.asFunction<VoidFromStringStringFn>();
|
||||
final ArchiveConversation = archiveConversation.asFunction<VoidFromStringIntFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = handle.toNativeUtf8();
|
||||
ArchiveConversation(u1, u1.length, u2, u2.length);
|
||||
ArchiveConversation(u1, u1.length, handle);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteContact(String profileOnion, String handle) {
|
||||
var deleteContact = library.lookup<NativeFunction<string_string_to_void_function>>("c_DeleteContact");
|
||||
void DeleteContact(String profileOnion, int handle) {
|
||||
var deleteContact = library.lookup<NativeFunction<string_int_to_void_function>>("c_DeleteContact");
|
||||
// ignore: non_constant_identifier_names
|
||||
final DeleteContact = deleteContact.asFunction<VoidFromStringStringFn>();
|
||||
final DeleteContact = deleteContact.asFunction<VoidFromStringIntFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = handle.toNativeUtf8();
|
||||
DeleteContact(u1, u1.length, u2, u2.length);
|
||||
DeleteContact(u1, u1.length, handle);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void UpdateMessageFlags(String profile, String handle, int index, int flags) {
|
||||
var updateMessageFlagsC = library.lookup<NativeFunction<void_from_string_string_int_int_function>>("c_UpdateMessageFlags");
|
||||
// ignore: non_constant_identifier_names
|
||||
final updateMessageFlags = updateMessageFlagsC.asFunction<VoidFromStringStringIntIntFn>();
|
||||
final utf8profile = profile.toNativeUtf8();
|
||||
final utf8handle = handle.toNativeUtf8();
|
||||
updateMessageFlags(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index, flags);
|
||||
malloc.free(utf8profile);
|
||||
malloc.free(utf8handle);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -557,7 +509,7 @@ class CwtchFfi implements Cwtch {
|
|||
final SetProfileAttribute = setProfileAttribute.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = profile.toNativeUtf8();
|
||||
final u2 = key.toNativeUtf8();
|
||||
final u3 = key.toNativeUtf8();
|
||||
final u3 = val.toNativeUtf8();
|
||||
SetProfileAttribute(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
|
@ -566,17 +518,30 @@ class CwtchFfi implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetContactAttribute(String profile, String contact, String key, String val) {
|
||||
var setContactAttribute = library.lookup<NativeFunction<void_from_string_string_string_string_function>>("c_SetContactAttribute");
|
||||
void SetConversationAttribute(String profile, int contact, String key, String val) {
|
||||
var setContactAttribute = library.lookup<NativeFunction<void_from_string_int_string_string_function>>("c_SetConversationAttribute");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SetContactAttribute = setContactAttribute.asFunction<VoidFromStringStringStringStringFn>();
|
||||
final SetContactAttribute = setContactAttribute.asFunction<VoidFromStringIntStringStringFn>();
|
||||
final u1 = profile.toNativeUtf8();
|
||||
final u2 = contact.toNativeUtf8();
|
||||
final u3 = key.toNativeUtf8();
|
||||
final u4 = key.toNativeUtf8();
|
||||
SetContactAttribute(u1, u1.length, u2, u2.length, u3, u3.length, u4, u4.length);
|
||||
final u4 = val.toNativeUtf8();
|
||||
SetContactAttribute(u1, u1.length, contact, u3, u3.length, u4, u4.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u3);
|
||||
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(u2);
|
||||
malloc.free(u3);
|
||||
malloc.free(u4);
|
||||
}
|
||||
|
@ -703,19 +668,17 @@ class CwtchFfi implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
Future GetMessageByContentHash(String profile, String handle, String contentHash) async {
|
||||
var getMessagesByContentHashC = library.lookup<NativeFunction<get_json_blob_from_str_str_str_function>>("c_GetMessagesByContentHash");
|
||||
Future GetMessageByContentHash(String profile, int handle, String contentHash) async {
|
||||
var getMessagesByContentHashC = library.lookup<NativeFunction<get_json_blob_from_str_int_string_function>>("c_GetMessagesByContentHash");
|
||||
// ignore: non_constant_identifier_names
|
||||
final GetMessagesByContentHash = getMessagesByContentHashC.asFunction<GetJsonBlobFromStrStrStrFn>();
|
||||
final GetMessagesByContentHash = getMessagesByContentHashC.asFunction<GetJsonBlobFromStrIntStringFn>();
|
||||
final utf8profile = profile.toNativeUtf8();
|
||||
final utf8handle = handle.toNativeUtf8();
|
||||
final utf8contentHash = contentHash.toNativeUtf8();
|
||||
Pointer<Utf8> jsonMessageBytes = GetMessagesByContentHash(utf8profile, utf8profile.length, utf8handle, utf8handle.length, utf8contentHash, utf8contentHash.length);
|
||||
Pointer<Utf8> jsonMessageBytes = GetMessagesByContentHash(utf8profile, utf8profile.length, handle, utf8contentHash, utf8contentHash.length);
|
||||
String jsonMessage = jsonMessageBytes.toDartString();
|
||||
|
||||
_UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(jsonMessageBytes);
|
||||
malloc.free(utf8profile);
|
||||
malloc.free(utf8handle);
|
||||
malloc.free(utf8contentHash);
|
||||
return jsonMessage;
|
||||
}
|
||||
|
@ -728,4 +691,17 @@ class CwtchFfi implements Cwtch {
|
|||
final Free = free.asFunction<FreeFn>();
|
||||
Free(ptr);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> GetMessageByID(String profile, int handle, int index) async {
|
||||
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_GetMessageByID");
|
||||
// ignore: non_constant_identifier_names
|
||||
final GetMessage = getMessageC.asFunction<GetJsonBlobFromStrIntIntFn>();
|
||||
final utf8profile = profile.toNativeUtf8();
|
||||
Pointer<Utf8> jsonMessageBytes = GetMessage(utf8profile, utf8profile.length, handle, index);
|
||||
String jsonMessage = jsonMessageBytes.toDartString();
|
||||
_UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(jsonMessageBytes);
|
||||
malloc.free(utf8profile);
|
||||
return jsonMessage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,11 +66,6 @@ class CwtchGomobile implements Cwtch {
|
|||
cwtchNotifier.handleMessage(call.method, obj);
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void SelectProfile(String onion) {
|
||||
cwtchPlatform.invokeMethod("SelectProfile", {"profile": onion});
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateProfile(String nick, String pass) {
|
||||
cwtchPlatform.invokeMethod("CreateProfile", {"nick": nick, "pass": pass});
|
||||
|
@ -87,9 +82,13 @@ class CwtchGomobile implements Cwtch {
|
|||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<dynamic> GetMessage(String profile, String handle, int index) {
|
||||
print("gomobile.dart GetMessage " + index.toString());
|
||||
return cwtchPlatform.invokeMethod("GetMessage", {"profile": profile, "contact": handle, "index": index});
|
||||
Future<dynamic> GetMessage(String profile, int conversation, int index) {
|
||||
return cwtchPlatform.invokeMethod("GetMessage", {"ProfileOnion": profile, "conversation": conversation, "index": index});
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<dynamic> GetMessageByID(String profile, int conversation, int id) {
|
||||
return cwtchPlatform.invokeMethod("GetMessageByID", {"ProfileOnion": profile, "conversation": conversation, "id": id});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -109,43 +108,43 @@ class CwtchGomobile implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void AcceptContact(String profileOnion, String contactHandle) {
|
||||
cwtchPlatform.invokeMethod("AcceptContact", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
||||
void AcceptContact(String profileOnion, int conversation) {
|
||||
cwtchPlatform.invokeMethod("AcceptContact", {"ProfileOnion": profileOnion, "conversation": conversation});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void BlockContact(String profileOnion, String contactHandle) {
|
||||
cwtchPlatform.invokeMethod("BlockContact", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
||||
void BlockContact(String profileOnion, int conversation) {
|
||||
cwtchPlatform.invokeMethod("BlockContact", {"ProfileOnion": profileOnion, "conversation": conversation});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendMessage(String profileOnion, String contactHandle, String message) {
|
||||
cwtchPlatform.invokeMethod("SendMessage", {"ProfileOnion": profileOnion, "handle": contactHandle, "message": message});
|
||||
void SendMessage(String profileOnion, int conversation, String message) {
|
||||
cwtchPlatform.invokeMethod("SendMessage", {"ProfileOnion": profileOnion, "conversation": conversation, "message": message});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendInvitation(String profileOnion, String contactHandle, String target) {
|
||||
cwtchPlatform.invokeMethod("SendInvitation", {"ProfileOnion": profileOnion, "handle": contactHandle, "target": target});
|
||||
void SendInvitation(String profileOnion, int conversation, int target) {
|
||||
cwtchPlatform.invokeMethod("SendInvitation", {"ProfileOnion": profileOnion, "conversation": conversation, "target": target});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ShareFile(String profileOnion, String contactHandle, String filepath) {
|
||||
cwtchPlatform.invokeMethod("ShareFile", {"ProfileOnion": profileOnion, "handle": contactHandle, "filepath": filepath});
|
||||
void ShareFile(String profileOnion, int conversation, String filepath) {
|
||||
cwtchPlatform.invokeMethod("ShareFile", {"ProfileOnion": profileOnion, "conversation": conversation, "filepath": filepath});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DownloadFile(String profileOnion, String contactHandle, String filepath, String manifestpath, String filekey) {
|
||||
cwtchPlatform.invokeMethod("DownloadFile", {"ProfileOnion": profileOnion, "handle": contactHandle, "filepath": filepath, "manifestpath": manifestpath, "filekey": filekey});
|
||||
void DownloadFile(String profileOnion, int conversation, String filepath, String manifestpath, String filekey) {
|
||||
cwtchPlatform.invokeMethod("DownloadFile", {"ProfileOnion": profileOnion, "conversation": conversation, "filepath": filepath, "manifestpath": manifestpath, "filekey": filekey});
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateDownloadableFile(String profileOnion, String contactHandle, String filenameSuggestion, String filekey) {
|
||||
cwtchPlatform.invokeMethod("CreateDownloadableFile", {"ProfileOnion": profileOnion, "handle": contactHandle, "filename": filenameSuggestion, "filekey": filekey});
|
||||
void CreateDownloadableFile(String profileOnion, int conversation, String filenameSuggestion, String filekey) {
|
||||
cwtchPlatform.invokeMethod("CreateDownloadableFile", {"ProfileOnion": profileOnion, "conversation": conversation, "filename": filenameSuggestion, "filekey": filekey});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -156,8 +155,8 @@ class CwtchGomobile implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profileOnion, String contactHandle, String filekey) {
|
||||
cwtchPlatform.invokeMethod("VerifyOrResumeDownload", {"ProfileOnion": profileOnion, "handle": contactHandle, "filekey": filekey});
|
||||
void VerifyOrResumeDownload(String profileOnion, int conversation, String filekey) {
|
||||
cwtchPlatform.invokeMethod("VerifyOrResumeDownload", {"ProfileOnion": profileOnion, "conversation": conversation, "filekey": filekey});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -172,18 +171,6 @@ class CwtchGomobile implements Cwtch {
|
|||
cwtchPlatform.invokeMethod("ImportBundle", {"ProfileOnion": profileOnion, "bundle": bundle});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetGroupAttribute(String profileOnion, String groupHandle, String key, String value) {
|
||||
cwtchPlatform.invokeMethod("SetGroupAttribute", {"ProfileOnion": profileOnion, "groupHandle": groupHandle, "key": key, "value": value});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void RejectInvite(String profileOnion, String groupHandle) {
|
||||
cwtchPlatform.invokeMethod("RejectInvite", {"ProfileOnion": profileOnion, "groupHandle": groupHandle});
|
||||
}
|
||||
|
||||
@override
|
||||
void CreateGroup(String profileOnion, String server, String groupName) {
|
||||
cwtchPlatform.invokeMethod("CreateGroup", {"ProfileOnion": profileOnion, "server": server, "groupName": groupName});
|
||||
|
@ -191,20 +178,14 @@ class CwtchGomobile implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteContact(String profileOnion, String handle) {
|
||||
cwtchPlatform.invokeMethod("DeleteContact", {"ProfileOnion": profileOnion, "handle": handle});
|
||||
void DeleteContact(String profileOnion, int conversation) {
|
||||
cwtchPlatform.invokeMethod("DeleteContact", {"ProfileOnion": profileOnion, "conversation": conversation});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ArchiveConversation(String profileOnion, String contactHandle) {
|
||||
cwtchPlatform.invokeMethod("ArchiveConversation", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
||||
}
|
||||
|
||||
@override
|
||||
void UpdateMessageFlags(String profile, String handle, int index, int flags) {
|
||||
print("gomobile.dart UpdateMessageFlags " + index.toString());
|
||||
cwtchPlatform.invokeMethod("UpdateMessageFlags", {"profile": profile, "contact": handle, "midx": index, "flags": flags});
|
||||
void ArchiveConversation(String profileOnion, int conversation) {
|
||||
cwtchPlatform.invokeMethod("ArchiveConversation", {"ProfileOnion": profileOnion, "conversation": conversation});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -215,8 +196,8 @@ class CwtchGomobile implements Cwtch {
|
|||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetContactAttribute(String profile, String contact, String key, String val) {
|
||||
cwtchPlatform.invokeMethod("SetContactAttribute", {"ProfileOnion": profile, "Contact": contact, "Key": key, "Val": val});
|
||||
void SetConversationAttribute(String profile, int conversation, String key, String val) {
|
||||
cwtchPlatform.invokeMethod("SetContactAttribute", {"ProfileOnion": profile, "conversation": conversation, "Key": key, "Val": val});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -273,14 +254,19 @@ class CwtchGomobile implements Cwtch {
|
|||
cwtchPlatform.invokeMethod("SetServerAttribute", {"ServerOnion": serverOnion, "Key": key, "Val": val});
|
||||
}
|
||||
|
||||
@override
|
||||
@override
|
||||
Future<void> Shutdown() async {
|
||||
print("gomobile.dart Shutdown");
|
||||
cwtchPlatform.invokeMethod("Shutdown", {});
|
||||
}
|
||||
|
||||
@override
|
||||
Future GetMessageByContentHash(String profile, String handle, String contentHash) {
|
||||
return cwtchPlatform.invokeMethod("GetMessageByContentHash", {"profile": profile, "contact": handle, "contentHash": contentHash});
|
||||
Future GetMessageByContentHash(String profile, int conversation, String contentHash) {
|
||||
return cwtchPlatform.invokeMethod("GetMessageByContentHash", {"ProfileOnion": profile, "conversation": conversation, "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});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:cwtch/config.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/widgets/messagerow.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cwtch/models/profileservers.dart';
|
||||
|
@ -29,7 +31,7 @@ class AppState extends ChangeNotifier {
|
|||
bool cwtchIsClosing = false;
|
||||
String appError = "";
|
||||
String? _selectedProfile;
|
||||
String? _selectedConversation;
|
||||
int? _selectedConversation;
|
||||
int _initialScrollIndex = 0;
|
||||
int _hoveredIndex = -1;
|
||||
int? _selectedIndex;
|
||||
|
@ -51,8 +53,8 @@ class AppState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
String? get selectedConversation => _selectedConversation;
|
||||
set selectedConversation(String? newVal) {
|
||||
int? get selectedConversation => _selectedConversation;
|
||||
set selectedConversation(int? newVal) {
|
||||
this._selectedConversation = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -172,8 +174,8 @@ class ContactListState extends ChangeNotifier {
|
|||
//} </todo>
|
||||
}
|
||||
|
||||
void updateLastMessageTime(String forOnion, DateTime newMessageTime) {
|
||||
var contact = getContact(forOnion);
|
||||
void updateLastMessageTime(int forIdentifier, DateTime newMessageTime) {
|
||||
var contact = getContact(forIdentifier);
|
||||
if (contact == null) return;
|
||||
|
||||
// Assert that the new time is after the current last message time AND that
|
||||
|
@ -191,18 +193,23 @@ class ContactListState extends ChangeNotifier {
|
|||
|
||||
List<ContactInfoState> get contacts => _contacts.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
||||
ContactInfoState? getContact(String onion) {
|
||||
int idx = _contacts.indexWhere((element) => element.onion == onion);
|
||||
ContactInfoState? getContact(int identifier) {
|
||||
int idx = _contacts.indexWhere((element) => element.identifier == identifier);
|
||||
return idx >= 0 ? _contacts[idx] : null;
|
||||
}
|
||||
|
||||
void removeContact(String onion) {
|
||||
int idx = _contacts.indexWhere((element) => element.onion == onion);
|
||||
void removeContact(int identifier) {
|
||||
int idx = _contacts.indexWhere((element) => element.identifier == identifier);
|
||||
if (idx >= 0) {
|
||||
_contacts.removeAt(idx);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
ContactInfoState? findContact(String byHandle) {
|
||||
int idx = _contacts.indexWhere((element) => element.onion == byHandle);
|
||||
return idx >= 0 ? _contacts[idx] : null;
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileInfoState extends ChangeNotifier {
|
||||
|
@ -238,7 +245,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
if (contactsJson != null && contactsJson != "" && contactsJson != "null") {
|
||||
List<dynamic> contacts = jsonDecode(contactsJson);
|
||||
this._contacts.addAll(contacts.map((contact) {
|
||||
return ContactInfoState(this.onion, contact["onion"],
|
||||
return ContactInfoState(this.onion, contact["identifier"], contact["onion"],
|
||||
nickname: contact["name"],
|
||||
status: contact["status"],
|
||||
imagePath: contact["picture"],
|
||||
|
@ -254,7 +261,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
|
||||
// dummy set to invoke sort-on-load
|
||||
if (this._contacts.num > 0) {
|
||||
this._contacts.updateLastMessageTime(this._contacts._contacts.first.onion, this._contacts._contacts.first.lastMessageTime);
|
||||
this._contacts.updateLastMessageTime(this._contacts._contacts.first.identifier, this._contacts._contacts.first.lastMessageTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,6 +348,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
} else {
|
||||
this._contacts.add(ContactInfoState(
|
||||
this.onion,
|
||||
contact["identifier"],
|
||||
contact["onion"],
|
||||
nickname: contact["name"],
|
||||
status: contact["status"],
|
||||
|
@ -494,8 +502,15 @@ ContactAuthorization stringToContactAuthorization(String authStr) {
|
|||
}
|
||||
}
|
||||
|
||||
class MessageCache {
|
||||
final MessageMetadata metadata;
|
||||
final String wrapper;
|
||||
MessageCache(this.metadata, this.wrapper);
|
||||
}
|
||||
|
||||
class ContactInfoState extends ChangeNotifier {
|
||||
final String profileOnion;
|
||||
final int identifier;
|
||||
final String onion;
|
||||
late String _nickname;
|
||||
|
||||
|
@ -507,6 +522,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
late int _totalMessages = 0;
|
||||
late DateTime _lastMessageTime;
|
||||
late Map<String, GlobalKey<MessageRowState>> keys;
|
||||
late List<MessageCache?> messageCache;
|
||||
int _newMarker = 0;
|
||||
DateTime _newMarkerClearAt = DateTime.now();
|
||||
|
||||
|
@ -515,7 +531,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
String? _server;
|
||||
late bool _archived;
|
||||
|
||||
ContactInfoState(this.profileOnion, this.onion,
|
||||
ContactInfoState(this.profileOnion, this.identifier, this.onion,
|
||||
{nickname = "",
|
||||
isGroup = false,
|
||||
authorization = ContactAuthorization.unknown,
|
||||
|
@ -538,6 +554,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
|
||||
this._server = server;
|
||||
this._archived = archived;
|
||||
this.messageCache = List.empty(growable: true);
|
||||
keys = Map<String, GlobalKey<MessageRowState>>();
|
||||
}
|
||||
|
||||
|
@ -593,7 +610,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
if (newVal > 0) {
|
||||
this._newMarker = newVal;
|
||||
} else {
|
||||
this._newMarkerClearAt = DateTime.now().add(const Duration(minutes:2));
|
||||
this._newMarkerClearAt = DateTime.now().add(const Duration(minutes: 2));
|
||||
}
|
||||
this._unreadMessages = newVal;
|
||||
notifyListeners();
|
||||
|
@ -608,12 +625,13 @@ class ContactInfoState extends ChangeNotifier {
|
|||
}
|
||||
return this._newMarker;
|
||||
}
|
||||
|
||||
// what's a getter that sometimes sets without a setter
|
||||
// that sometimes doesn't set
|
||||
set newMarker(int newVal) {
|
||||
// only unreadMessages++ can set newMarker = 1;
|
||||
// avoids drawing a marker when the convo is already open
|
||||
if (newVal > 1) {
|
||||
if (newVal >= 1) {
|
||||
this._newMarker = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -649,11 +667,37 @@ class ContactInfoState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
GlobalKey<MessageRowState> getMessageKey(String index) {
|
||||
GlobalKey<MessageRowState> getMessageKey(int conversation, int message) {
|
||||
String index = "c: " + conversation.toString() + " m:" + message.toString();
|
||||
if (keys[index] == null) {
|
||||
keys[index] = GlobalKey<MessageRowState>();
|
||||
}
|
||||
GlobalKey<MessageRowState> ret = keys[index]!;
|
||||
return ret;
|
||||
}
|
||||
|
||||
GlobalKey<MessageRowState>? getMessageKeyOrFail(int conversation, int message) {
|
||||
String index = "c: " + conversation.toString() + " m:" + message.toString();
|
||||
|
||||
if (keys[index] == null) {
|
||||
return null;
|
||||
}
|
||||
GlobalKey<MessageRowState> ret = keys[index]!;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void updateMessageCache(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, String data) {
|
||||
this.messageCache.insert(0, MessageCache(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false), data));
|
||||
this.totalMessages += 1;
|
||||
}
|
||||
|
||||
void bumpMessageCache() {
|
||||
sarah marked this conversation as resolved
dan
commented
ideally internal. the public setter for totalMessages can call, and updateMessageCaches should internally inc totalMessages ideally internal. the public setter for totalMessages can call, and updateMessageCaches should internally inc totalMessages
|
||||
this.messageCache.insert(0, null);
|
||||
this.totalMessages += 1;
|
||||
}
|
||||
|
||||
void ackCache(int messageID) {
|
||||
this.messageCache.firstWhere((element) => element?.metadata.messageID == messageID)?.metadata.ackd = true;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cwtch/config.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -27,15 +28,54 @@ const GroupConversationHandleLength = 32;
|
|||
|
||||
abstract class Message {
|
||||
MessageMetadata getMetadata();
|
||||
Widget getWidget(BuildContext context);
|
||||
Widget getWidget(BuildContext context, Key key);
|
||||
Widget getPreviewWidget(BuildContext context);
|
||||
}
|
||||
|
||||
Future<Message> messageHandler(BuildContext context, String profileOnion, String contactHandle, int index) {
|
||||
Message compileOverlay(MessageMetadata metadata, String messageData) {
|
||||
try {
|
||||
var rawMessageEnvelopeFuture = Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, index);
|
||||
dynamic message = jsonDecode(messageData);
|
||||
var content = message['d'] as dynamic;
|
||||
var overlay = int.parse(message['o'].toString());
|
||||
|
||||
switch (overlay) {
|
||||
case TextMessageOverlay:
|
||||
return TextMessage(metadata, content);
|
||||
case SuggestContactOverlay:
|
||||
case InviteGroupOverlay:
|
||||
return InviteMessage(overlay, metadata, content);
|
||||
case QuotedMessageOverlay:
|
||||
return QuotedMessage(metadata, content);
|
||||
case FileShareOverlay:
|
||||
return FileMessage(metadata, content);
|
||||
default:
|
||||
// Metadata is valid, content is not..
|
||||
return MalformedMessage(metadata);
|
||||
}
|
||||
} catch (e) {
|
||||
return MalformedMessage(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Message> messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, int index, {bool byID = false}) {
|
||||
var cache = Provider.of<ProfileInfoState>(context).contactList.getContact(conversationIdentifier)?.messageCache;
|
||||
if (cache != null && cache.length > index) {
|
||||
if (cache[index] != null) {
|
||||
return Future.value(compileOverlay(cache[index]!.metadata, cache[index]!.wrapper));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Future<dynamic> rawMessageEnvelopeFuture;
|
||||
|
||||
if (byID) {
|
||||
rawMessageEnvelopeFuture = Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessageByID(profileOnion, conversationIdentifier, index);
|
||||
} else {
|
||||
rawMessageEnvelopeFuture = Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, conversationIdentifier, index);
|
||||
}
|
||||
|
||||
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
|
||||
var metadata = MessageMetadata(profileOnion, contactHandle, index, DateTime.now(), "", "", null, 0, false, true);
|
||||
var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, DateTime.now(), "", "", "", <String, String>{}, false, true);
|
||||
try {
|
||||
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
|
||||
// There are 2 conditions in which this error condition can be met:
|
||||
|
@ -50,72 +90,48 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, String
|
|||
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
||||
return Future.delayed(Duration(seconds: 2), () {
|
||||
print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug.");
|
||||
return messageHandler(context, profileOnion, contactHandle, index).then((value) => value);
|
||||
return messageHandler(context, profileOnion, conversationIdentifier, -1, byID: byID).then((value) => value);
|
||||
});
|
||||
}
|
||||
|
||||
// Construct the initial metadata
|
||||
var messageID = messageWrapper['ID'];
|
||||
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;
|
||||
String? signature;
|
||||
// If this is a group, store the signature
|
||||
if (contactHandle.length == GroupConversationHandleLength) {
|
||||
signature = messageWrapper['Signature'];
|
||||
}
|
||||
metadata = MessageMetadata(profileOnion, contactHandle, index, timestamp, senderHandle, senderImage, signature, flags, ackd, error);
|
||||
var signature = messageWrapper['Signature'];
|
||||
metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error);
|
||||
|
||||
dynamic message = jsonDecode(messageWrapper['Message']);
|
||||
var content = message['d'] as dynamic;
|
||||
var overlay = int.parse(message['o'].toString());
|
||||
|
||||
switch (overlay) {
|
||||
case TextMessageOverlay:
|
||||
return TextMessage(metadata, content);
|
||||
case SuggestContactOverlay:
|
||||
case InviteGroupOverlay:
|
||||
return InviteMessage(overlay, metadata, content);
|
||||
case QuotedMessageOverlay:
|
||||
return QuotedMessage(metadata, content);
|
||||
case FileShareOverlay:
|
||||
return FileMessage(metadata, content);
|
||||
default:
|
||||
// Metadata is valid, content is not..
|
||||
return MalformedMessage(metadata);
|
||||
}
|
||||
return compileOverlay(metadata, messageWrapper['Message']);
|
||||
} catch (e) {
|
||||
print("an error! " + e.toString());
|
||||
EnvironmentConfig.debugLog("an error! " + e.toString());
|
||||
return MalformedMessage(metadata);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
return Future.value(MalformedMessage(MessageMetadata(profileOnion, contactHandle, index, DateTime.now(), "", "", null, 0, false, true)));
|
||||
return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, -1, DateTime.now(), "", "", "", <String, String>{}, false, true)));
|
||||
}
|
||||
}
|
||||
|
||||
class MessageMetadata extends ChangeNotifier {
|
||||
// meta-metadata
|
||||
final String profileOnion;
|
||||
final String contactHandle;
|
||||
final int messageIndex;
|
||||
final int conversationIdentifier;
|
||||
final int messageID;
|
||||
|
||||
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) {
|
||||
|
@ -129,5 +145,5 @@ class MessageMetadata extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
MessageMetadata(this.profileOnion, this.contactHandle, this.messageIndex, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._flags, this._ackd, this._error);
|
||||
MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error);
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@ class FileMessage extends Message {
|
|||
FileMessage(this.metadata, this.content);
|
||||
|
||||
@override
|
||||
Widget getWidget(BuildContext context) {
|
||||
Widget getWidget(BuildContext context, Key key) {
|
||||
return ChangeNotifierProvider.value(
|
||||
key: key,
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
dynamic shareObj = jsonDecode(this.content);
|
||||
if (shareObj == null) {
|
||||
return MessageRow(MalformedBubble());
|
||||
|
@ -35,7 +35,7 @@ class FileMessage extends Message {
|
|||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
|
||||
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize), key: key);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -17,18 +17,18 @@ class InviteMessage extends Message {
|
|||
InviteMessage(this.overlay, this.metadata, this.content);
|
||||
|
||||
@override
|
||||
Widget getWidget(BuildContext context) {
|
||||
Widget getWidget(BuildContext context, Key key) {
|
||||
return ChangeNotifierProvider.value(
|
||||
key: key,
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
String inviteTarget;
|
||||
String inviteNick;
|
||||
String invite = this.content;
|
||||
|
||||
if (this.content.length == TorV3ContactHandleLength) {
|
||||
inviteTarget = this.content;
|
||||
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
|
||||
var targetContact = Provider.of<ProfileInfoState>(context).contactList.findContact(inviteTarget);
|
||||
inviteNick = targetContact == null ? this.content : targetContact.nickname;
|
||||
} else {
|
||||
var parts = this.content.toString().split("||");
|
||||
|
@ -37,10 +37,10 @@ class InviteMessage extends Message {
|
|||
inviteTarget = jsonObj['GroupID'];
|
||||
inviteNick = jsonObj['GroupName'];
|
||||
} else {
|
||||
return MessageRow(MalformedBubble());
|
||||
return MessageRow(MalformedBubble(), key: key);
|
||||
}
|
||||
}
|
||||
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite), key: key);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ class InviteMessage extends Message {
|
|||
String invite = this.content;
|
||||
if (this.content.length == TorV3ContactHandleLength) {
|
||||
inviteTarget = this.content;
|
||||
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
|
||||
var targetContact = Provider.of<ProfileInfoState>(context).contactList.findContact(inviteTarget);
|
||||
inviteNick = targetContact == null ? this.content : targetContact.nickname;
|
||||
} else {
|
||||
var parts = this.content.toString().split("||");
|
||||
|
|
|
@ -9,11 +9,11 @@ class MalformedMessage extends Message {
|
|||
MalformedMessage(this.metadata);
|
||||
|
||||
@override
|
||||
Widget getWidget(BuildContext context) {
|
||||
Widget getWidget(BuildContext context, Key key) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (context, child) {
|
||||
return MessageRow(MalformedBubble());
|
||||
return MessageRow(MalformedBubble(), key: key);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ class QuotedMessage extends Message {
|
|||
dynamic message = jsonDecode(this.content);
|
||||
return Text(message["body"]);
|
||||
} catch (e) {
|
||||
return MalformedMessage(this.metadata).getWidget(context);
|
||||
return MalformedMessage(this.metadata).getWidget(context, Key("malformed"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -62,16 +62,15 @@ class QuotedMessage extends Message {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget getWidget(BuildContext context) {
|
||||
Widget getWidget(BuildContext context, Key key) {
|
||||
try {
|
||||
dynamic message = jsonDecode(this.content);
|
||||
|
||||
if (message["body"] == null || message["quotedHash"] == null) {
|
||||
return MalformedMessage(this.metadata).getWidget(context);
|
||||
return MalformedMessage(this.metadata).getWidget(context, key);
|
||||
}
|
||||
|
||||
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.contactHandle, message["quotedHash"]);
|
||||
int messageIndex = metadata.messageIndex;
|
||||
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.conversationIdentifier, message["quotedHash"]);
|
||||
Future<LocallyIndexedMessage?> quotedMessage = quotedMessagePotentials.then((matchingMessages) {
|
||||
if (matchingMessages == "[]") {
|
||||
return null;
|
||||
|
@ -81,9 +80,7 @@ class QuotedMessage extends Message {
|
|||
// message
|
||||
try {
|
||||
var list = (jsonDecode(matchingMessages) as List<dynamic>).map((data) => LocallyIndexedMessage.fromJson(data)).toList();
|
||||
LocallyIndexedMessage candidate = list.reversed.firstWhere((element) => messageIndex < element.index, orElse: () {
|
||||
return list.firstWhere((element) => messageIndex > element.index);
|
||||
});
|
||||
LocallyIndexedMessage candidate = list.reversed.first;
|
||||
return candidate;
|
||||
} catch (e) {
|
||||
// Malformed Message will be returned...
|
||||
|
@ -94,18 +91,17 @@ class QuotedMessage extends Message {
|
|||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
return MessageRow(
|
||||
QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
|
||||
if (localIndex != null) {
|
||||
return messageHandler(context, metadata.profileOnion, metadata.contactHandle, localIndex.index);
|
||||
return messageHandler(context, metadata.profileOnion, metadata.conversationIdentifier, localIndex.index);
|
||||
}
|
||||
return MalformedMessage(this.metadata);
|
||||
})),
|
||||
key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
key: key);
|
||||
});
|
||||
} catch (e) {
|
||||
return MalformedMessage(this.metadata).getWidget(context);
|
||||
return MalformedMessage(this.metadata).getWidget(context, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/models/messages/malformedmessage.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:cwtch/widgets/messagebubble.dart';
|
||||
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||
import 'package:cwtch/widgets/messagerow.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -28,12 +31,14 @@ class TextMessage extends Message {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget getWidget(BuildContext context) {
|
||||
Widget getWidget(BuildContext context, Key key) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
return MessageRow(MessageBubble(this.content), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
return MessageRow(
|
||||
MessageBubble(this.content),
|
||||
key: key,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,7 @@ class ServerListState extends ChangeNotifier {
|
|||
if (idx >= 0) {
|
||||
_servers[idx] = sis;
|
||||
} else {
|
||||
_servers.add(ServerInfoState(onion: onion,
|
||||
serverBundle: serverBundle,
|
||||
running: running,
|
||||
description: description,
|
||||
autoStart: autoStart,
|
||||
isEncrypted: isEncrypted));
|
||||
_servers.add(ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted));
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -37,7 +32,7 @@ class ServerListState extends ChangeNotifier {
|
|||
void updateServer(String onion, String serverBundle, bool running, String description, bool autoStart, bool isEncrypted) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
if (idx >= 0) {
|
||||
_servers[idx] = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted);
|
||||
_servers[idx] = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted);
|
||||
} else {
|
||||
print("Tried to update server list without a starting state...this is probably an error");
|
||||
}
|
||||
|
|
|
@ -296,11 +296,13 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
// Profile Editing
|
||||
if (ctrlrPass.value.text.isEmpty) {
|
||||
// Don't update password, only update name
|
||||
Provider.of<ProfileInfoState>(context, listen: false).nickname = ctrlrNick.value.text;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
// At this points passwords have been validated to be the same and not empty
|
||||
// Update both password and name, even if name hasn't been changed...
|
||||
Provider.of<ProfileInfoState>(context, listen: false).nickname = ctrlrNick.value.text;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
|
||||
final updatePasswordEvent = {
|
||||
"EventType": "ChangePassword",
|
||||
|
|
|
@ -53,7 +53,6 @@ class _AddEditServerViewState extends State<AddEditServerView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: ctrlrOnion.text.isEmpty ? Text(AppLocalizations.of(context)!.addServerTitle) : Text(AppLocalizations.of(context)!.editServerTitle),
|
||||
|
@ -82,232 +81,222 @@ class _AddEditServerViewState extends State<AddEditServerView> {
|
|||
child: Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
margin: EdgeInsets.fromLTRB(30, 0, 30, 10),
|
||||
padding: EdgeInsets.fromLTRB(20, 0 , 20, 10),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
margin: EdgeInsets.fromLTRB(30, 0, 30, 10),
|
||||
padding: EdgeInsets.fromLTRB(20, 0, 20, 10),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
// Onion
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.serverAddress),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
SelectableText(serverInfoState.onion)
|
||||
])),
|
||||
|
||||
// Onion
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.serverAddress),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
SelectableText(
|
||||
serverInfoState.onion
|
||||
)
|
||||
])),
|
||||
// Description
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
|
||||
Text(AppLocalizations.of(context)!.serverDescriptionDescription),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: ctrlrDesc,
|
||||
labelText: "Description",
|
||||
autofocus: false,
|
||||
)
|
||||
]),
|
||||
|
||||
// Description
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
|
||||
Text(AppLocalizations.of(context)!.serverDescriptionDescription),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: ctrlrDesc,
|
||||
labelText: "Description",
|
||||
autofocus: false,
|
||||
)
|
||||
]),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
// Enabled
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription),
|
||||
value: serverInfoState.running,
|
||||
onChanged: (bool value) {
|
||||
serverInfoState.setRunning(value);
|
||||
if (value) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
|
||||
} else {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.StopServer(serverInfoState.onion);
|
||||
}
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor()),
|
||||
)),
|
||||
|
||||
// Enabled
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription),
|
||||
value: serverInfoState.running,
|
||||
onChanged: (bool value) {
|
||||
serverInfoState.setRunning(value);
|
||||
if (value) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
|
||||
} else {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.StopServer(serverInfoState.onion);
|
||||
}
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor()),
|
||||
)),
|
||||
// Auto start
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription),
|
||||
value: serverInfoState.autoStart,
|
||||
onChanged: (bool value) {
|
||||
serverInfoState.setAutostart(value);
|
||||
|
||||
// Auto start
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription),
|
||||
value: serverInfoState.autoStart,
|
||||
onChanged: (bool value) {
|
||||
serverInfoState.setAutostart(value);
|
||||
if (!serverInfoState.onion.isEmpty) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false");
|
||||
}
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor()),
|
||||
),
|
||||
|
||||
if (! serverInfoState.onion.isEmpty) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false");
|
||||
}
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor()),
|
||||
),
|
||||
// ***** Password *****
|
||||
|
||||
// use password toggle
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Checkbox(
|
||||
value: usePassword,
|
||||
fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor()),
|
||||
activeColor: settings.current().defaultButtonActiveColor(),
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.radioUsePassword,
|
||||
style: TextStyle(color: settings.current().mainTextColor()),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription,
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
])),
|
||||
|
||||
// ***** Password *****
|
||||
// current password
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrOldPass,
|
||||
autoFillHints: [AutofillHints.newPassword],
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.isEncrypted && serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (Provider.of<ErrorHandler>(context).deletedServerError == true) {
|
||||
return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
])),
|
||||
|
||||
// use password toggle
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Checkbox(
|
||||
value: usePassword,
|
||||
fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor()),
|
||||
activeColor: settings.current().defaultButtonActiveColor(),
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.radioUsePassword,
|
||||
style: TextStyle(color: settings.current().mainTextColor()),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription,
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
])),
|
||||
// new passwords 1 & 2
|
||||
Visibility(
|
||||
// Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check
|
||||
visible: serverInfoState.onion.isEmpty && usePassword,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass,
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass2.value.text) {
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass2,
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass.value.text) {
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
]),
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
||||
// current password
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrOldPass,
|
||||
autoFillHints: [AutofillHints.newPassword],
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.isEncrypted &&
|
||||
serverInfoState.onion.isEmpty &&
|
||||
value.isEmpty &&
|
||||
usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (Provider.of<ErrorHandler>(context).deletedServerError == true) {
|
||||
return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
])),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed,
|
||||
child: Text(
|
||||
serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
showAlertDialog(context);
|
||||
},
|
||||
icon: Icon(Icons.delete_forever),
|
||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
||||
))
|
||||
]))
|
||||
|
||||
// new passwords 1 & 2
|
||||
Visibility(
|
||||
// Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check
|
||||
visible: serverInfoState.onion.isEmpty && usePassword,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass,
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass2.value.text) {
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass2,
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass.value.text) {
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
]),
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed,
|
||||
child: Text(
|
||||
serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
showAlertDialog(context);
|
||||
},
|
||||
icon: Icon(Icons.delete_forever),
|
||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
||||
))
|
||||
]))
|
||||
|
||||
// ***** END Password *****
|
||||
|
||||
]))))));
|
||||
// ***** END Password *****
|
||||
]))))));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -318,29 +307,20 @@ class _AddEditServerViewState extends State<AddEditServerView> {
|
|||
// match (and are provided if the user has requested an encrypted profile).
|
||||
if (_formKey.currentState!.validate()) {
|
||||
if (usePassword) {
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
|
||||
} else {
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
void _savePressed() {
|
||||
|
||||
var server = Provider.of<ServerInfoState>(context, listen: false);
|
||||
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text);
|
||||
server.setDescription(ctrlrDesc.text);
|
||||
|
||||
|
||||
if (_formKey.currentState!.validate()) {
|
||||
// TODO support change password
|
||||
}
|
||||
|
@ -358,16 +338,11 @@ class _AddEditServerViewState extends State<AddEditServerView> {
|
|||
Widget continueButton = ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
|
||||
onPressed: () {
|
||||
var onion = Provider
|
||||
.of<ServerInfoState>(context, listen: false)
|
||||
.onion;
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.DeleteServer(onion, Provider.of<ServerInfoState>(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword);
|
||||
var onion = Provider.of<ServerInfoState>(context, listen: false).onion;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteServer(onion, Provider.of<ServerInfoState>(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword);
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() {
|
||||
() {
|
||||
if (globalErrorHandler.deletedServerSuccess) {
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteServerSuccess + ":" + onion));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
|
|
|
@ -22,7 +22,7 @@ class ContactsView extends StatefulWidget {
|
|||
}
|
||||
|
||||
// selectConversation can be called from anywhere to set the active conversation
|
||||
void selectConversation(BuildContext context, String handle) {
|
||||
void selectConversation(BuildContext context, int handle) {
|
||||
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
|
||||
var initialIndex = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages;
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0;
|
||||
|
@ -36,7 +36,7 @@ void selectConversation(BuildContext context, String handle) {
|
|||
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(context, handle);
|
||||
}
|
||||
|
||||
void _pushMessageView(BuildContext context, String handle) {
|
||||
void _pushMessageView(BuildContext context, int handle) {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
|
@ -112,7 +112,6 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
|
||||
}));
|
||||
|
||||
|
||||
// TODO servers
|
||||
|
||||
// Search contacts
|
||||
|
|
|
@ -34,8 +34,8 @@ class _DoubleColumnViewState extends State<DoubleColumnView> {
|
|||
MultiProvider(providers: [
|
||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
|
||||
ChangeNotifierProvider.value(
|
||||
value: flwtch.selectedConversation != null ? Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation!)! : ContactInfoState("", "")),
|
||||
], child: Container(key: Key(flwtch.selectedConversation??"never_this"), child: MessageView())),
|
||||
value: flwtch.selectedConversation != null ? Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation!)! : ContactInfoState("", -1, "")),
|
||||
], child: Container(key: Key(flwtch.selectedConversation!.toString()), child: MessageView())),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -191,9 +191,8 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
secondary: Icon(CwtchIcons.enable_groups, color: settings.current().mainTextColor()),
|
||||
),
|
||||
Visibility(
|
||||
visible: !Platform.isAndroid && !Platform.isIOS,
|
||||
child:
|
||||
SwitchListTile(
|
||||
visible: !Platform.isAndroid && !Platform.isIOS,
|
||||
child: SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.settingServers, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.settingServersDescription),
|
||||
value: settings.isExperimentEnabled(ServerManagementExperiment),
|
||||
|
|
|
@ -78,9 +78,9 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
readonly: false,
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).identifier;
|
||||
Provider.of<ContactInfoState>(context, listen: false).nickname = ctrlrNick.text;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetGroupAttribute(profileOnion, handle, "local.name", ctrlrNick.text);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, handle, "profile.name", ctrlrNick.text);
|
||||
// todo translations
|
||||
final snackBar = SnackBar(content: Text("Group Nickname changed successfully"));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
|
@ -140,7 +140,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
var handle = 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.ArchiveConversation(profileOnion, handle);
|
||||
|
@ -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).onion;
|
||||
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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:cwtch/config.dart';
|
||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/models/messages/quotedmessage.dart';
|
||||
|
@ -33,7 +34,7 @@ class MessageView extends StatefulWidget {
|
|||
class _MessageViewState extends State<MessageView> {
|
||||
final ctrlrCompose = TextEditingController();
|
||||
final focusNode = FocusNode();
|
||||
String selectedContact = "";
|
||||
int selectedContact = -1;
|
||||
ItemPositionsListener scrollListener = ItemPositionsListener.create();
|
||||
ItemScrollController scrollController = ItemScrollController();
|
||||
|
||||
|
@ -41,10 +42,10 @@ class _MessageViewState extends State<MessageView> {
|
|||
void initState() {
|
||||
scrollListener.itemPositions.addListener(() {
|
||||
if (scrollListener.itemPositions.value.length != 0 &&
|
||||
Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true &&
|
||||
scrollListener.itemPositions.value.any((element) => element.index == 0)) {
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
|
||||
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
|
||||
Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true &&
|
||||
scrollListener.itemPositions.value.any((element) => element.index == 0)) {
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
|
||||
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
|
@ -168,7 +169,7 @@ class _MessageViewState extends State<MessageView> {
|
|||
if (Provider.of<AppState>(context, listen: false).selectedConversation != null && Provider.of<AppState>(context, listen: false).selectedIndex != null) {
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.GetMessage(Provider.of<AppState>(context, listen: false).selectedProfile!, Provider.of<AppState>(context, listen: false).selectedConversation!,
|
||||
.GetMessageByID(Provider.of<AppState>(context, listen: false).selectedProfile!, Provider.of<AppState>(context, listen: false).selectedConversation!,
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex!)
|
||||
.then((data) {
|
||||
try {
|
||||
|
@ -180,7 +181,7 @@ class _MessageViewState extends State<MessageView> {
|
|||
ChatMessage cm = new ChatMessage(o: QuotedMessageOverlay, d: quotedMessage);
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
|
||||
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, jsonEncode(cm));
|
||||
} catch (e) {}
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||
_sendMessageHelper();
|
||||
|
@ -189,7 +190,7 @@ class _MessageViewState extends State<MessageView> {
|
|||
ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: ctrlrCompose.value.text);
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
|
||||
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, jsonEncode(cm));
|
||||
_sendMessageHelper();
|
||||
}
|
||||
}
|
||||
|
@ -198,14 +199,14 @@ class _MessageViewState extends State<MessageView> {
|
|||
void _sendInvitation([String? ignoredParam]) {
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.SendInvitation(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, this.selectedContact);
|
||||
.SendInvitation(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, this.selectedContact);
|
||||
_sendMessageHelper();
|
||||
}
|
||||
|
||||
void _sendFile(String filePath) {
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.ShareFile(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, filePath);
|
||||
.ShareFile(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, filePath);
|
||||
_sendMessageHelper();
|
||||
}
|
||||
|
||||
|
@ -213,10 +214,10 @@ class _MessageViewState extends State<MessageView> {
|
|||
ctrlrCompose.clear();
|
||||
focusNode.requestFocus();
|
||||
Future.delayed(const Duration(milliseconds: 80), () {
|
||||
Provider.of<ContactInfoState>(context, listen: false).totalMessages++;
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(Provider.of<ContactInfoState>(context, listen: false).identifier)?.bumpMessageCache();
|
||||
Provider.of<ContactInfoState>(context, listen: false).newMarker++;
|
||||
// Resort the contact list...
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.updateLastMessageTime(Provider.of<ContactInfoState>(context, listen: false).onion, DateTime.now());
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.updateLastMessageTime(Provider.of<ContactInfoState>(context, listen: false).identifier, DateTime.now());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -268,7 +269,8 @@ class _MessageViewState extends State<MessageView> {
|
|||
var children;
|
||||
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
|
||||
var quoted = FutureBuilder(
|
||||
future: messageHandler(context, Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!),
|
||||
future:
|
||||
messageHandler(context, Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!, byID: true),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var message = snapshot.data! as Message;
|
||||
|
@ -352,7 +354,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(
|
||||
|
@ -361,7 +363,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);
|
||||
|
|
|
@ -69,14 +69,9 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
readonly: false,
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var onion = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
var conversation = Provider.of<ContactInfoState>(context, listen: false).identifier;
|
||||
Provider.of<ContactInfoState>(context, listen: false).nickname = ctrlrNick.text;
|
||||
final setPeerAttribute = {
|
||||
"EventType": "SetPeerAttribute",
|
||||
"Data": {"RemotePeer": onion, "Key": "local.name", "Data": ctrlrNick.text},
|
||||
};
|
||||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, conversation, "profile.name", ctrlrNick.text);
|
||||
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
},
|
||||
|
@ -200,7 +195,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
var handle = 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.ArchiveConversation(profileOnion, handle);
|
||||
|
@ -239,7 +234,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
child: Text(AppLocalizations.of(context)!.yesLeave),
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
var handle = 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);
|
||||
|
|
|
@ -57,9 +57,9 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(child: Text(MediaQuery.of(context).size.width > 600 ?
|
||||
AppLocalizations.of(context)!.titleManageProfiles : AppLocalizations.of(context)!.titleManageProfilesShort,
|
||||
style: TextStyle(color: settings.current().mainTextColor())))
|
||||
Expanded(
|
||||
child: Text(MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.titleManageProfiles : AppLocalizations.of(context)!.titleManageProfilesShort,
|
||||
style: TextStyle(color: settings.current().mainTextColor())))
|
||||
]),
|
||||
actions: getActions(),
|
||||
),
|
||||
|
@ -238,7 +238,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
if (tiles.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.unlockProfileTip,
|
||||
AppLocalizations.of(context)!.unlockProfileTip,
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
}
|
||||
|
|
|
@ -30,44 +30,45 @@ class _ServersView extends State<ServersView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text( MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.serversManagerTitleLong : AppLocalizations.of(context)!.serversManagerTitleShort),
|
||||
actions: getActions(),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddServer,
|
||||
tooltip: AppLocalizations.of(context)!.addServerTooltip,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
semanticLabel: AppLocalizations.of(context)!.addServerTooltip,
|
||||
appBar: AppBar(
|
||||
title: Text(MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.serversManagerTitleLong : AppLocalizations.of(context)!.serversManagerTitleShort),
|
||||
actions: getActions(),
|
||||
),
|
||||
),
|
||||
body: Consumer<ServerListState>(
|
||||
builder: (context, svrs, child) {
|
||||
final tiles = svrs.servers.map((ServerInfoState server) {
|
||||
return ChangeNotifierProvider<ServerInfoState>.value(
|
||||
value: server,
|
||||
builder: (context, child) => RepaintBoundary(child: ServerRow()),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddServer,
|
||||
tooltip: AppLocalizations.of(context)!.addServerTooltip,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
semanticLabel: AppLocalizations.of(context)!.addServerTooltip,
|
||||
),
|
||||
),
|
||||
body: Consumer<ServerListState>(
|
||||
builder: (context, svrs, child) {
|
||||
final tiles = svrs.servers.map(
|
||||
(ServerInfoState server) {
|
||||
return ChangeNotifierProvider<ServerInfoState>.value(
|
||||
value: server,
|
||||
builder: (context, child) => RepaintBoundary(child: ServerRow()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final divided = ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: tiles,
|
||||
).toList();
|
||||
|
||||
if (tiles.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.unlockServerTip,
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
}
|
||||
|
||||
return ListView(children: divided);
|
||||
},
|
||||
);
|
||||
|
||||
final divided = ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: tiles,
|
||||
).toList();
|
||||
|
||||
if (tiles.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.unlockServerTip,
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
}
|
||||
|
||||
return ListView(children: divided);
|
||||
},
|
||||
));
|
||||
));
|
||||
}
|
||||
|
||||
List<Widget> getActions() {
|
||||
|
@ -93,41 +94,41 @@ class _ServersView extends State<ServersView> {
|
|||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
height: 200, // bespoke value courtesy of the [TextField] docs
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(context)!.enterServerPassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
autofocus: true,
|
||||
controller: ctrlrPassword,
|
||||
action: unlock,
|
||||
validator: (value) {},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
Spacer(),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
|
||||
onPressed: () {
|
||||
unlock(ctrlrPassword.value.text);
|
||||
},
|
||||
)),
|
||||
Spacer()
|
||||
]),
|
||||
],
|
||||
))),
|
||||
)));
|
||||
height: 200, // bespoke value courtesy of the [TextField] docs
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(context)!.enterServerPassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
autofocus: true,
|
||||
controller: ctrlrPassword,
|
||||
action: unlock,
|
||||
validator: (value) {},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
Spacer(),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
|
||||
onPressed: () {
|
||||
unlock(ctrlrPassword.value.text);
|
||||
},
|
||||
)),
|
||||
Spacer()
|
||||
]),
|
||||
],
|
||||
))),
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -141,9 +142,11 @@ class _ServersView extends State<ServersView> {
|
|||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [ChangeNotifierProvider<ServerInfoState>(
|
||||
create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true),
|
||||
)],
|
||||
providers: [
|
||||
ChangeNotifierProvider<ServerInfoState>(
|
||||
create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true),
|
||||
)
|
||||
],
|
||||
child: AddEditServerView(),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -105,24 +105,26 @@ class _ContactRowState extends State<ContactRow> {
|
|||
),
|
||||
]),
|
||||
onTap: () {
|
||||
selectConversation(context, contact.onion);
|
||||
selectConversation(context, contact.identifier);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
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).onion);
|
||||
.AcceptContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier);
|
||||
}
|
||||
|
||||
void _btnReject() {
|
||||
ContactInfoState contact = Provider.of<ContactInfoState>(context, listen: false);
|
||||
if (contact.isGroup == true) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.RejectInvite(Provider.of<ContactInfoState>(context, listen: false).profileOnion, contact.onion);
|
||||
// FIXME This flow is incrorect. Groups never just show up on the contact list anymore
|
||||
Provider.of<ProfileInfoState>(context, listen: false).removeContact(contact.onion);
|
||||
} else {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.BlockContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, contact.onion);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.BlockContact(Provider.of<ContactInfoState>(context, listen: false).profileOnion, contact.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
@ -49,7 +50,7 @@ class FileBubbleState extends State<FileBubble> {
|
|||
// If the sender is not us, then we want to give them a nickname...
|
||||
var senderDisplayStr = "";
|
||||
if (!fromMe) {
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
if (contact != null) {
|
||||
senderDisplayStr = contact.nickname;
|
||||
} else {
|
||||
|
@ -89,15 +90,15 @@ class FileBubbleState extends State<FileBubble> {
|
|||
} else if (flagStarted) {
|
||||
// in this case, the download was done in a previous application launch,
|
||||
// so we probably have to request an info lookup
|
||||
if (!Provider.of<ProfileInfoState>(context).downloadInterrupted(widget.fileKey()) ) {
|
||||
if (!Provider.of<ProfileInfoState>(context).downloadInterrupted(widget.fileKey())) {
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F');
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CheckDownloadStatus(Provider.of<ProfileInfoState>(context, listen: false).onion, widget.fileKey());
|
||||
} else {
|
||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
|
||||
wdgDecorations = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:[Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))]
|
||||
);
|
||||
wdgDecorations = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),
|
||||
ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
wdgDecorations = Center(
|
||||
|
@ -146,15 +147,17 @@ 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.CreateDownloadableFile(profileOnion, handle, widget.nameSuggestion, widget.fileKey());
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
selectedFileName = await saveFile(
|
||||
|
@ -162,12 +165,15 @@ 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.DownloadFile(profileOnion, handle, file.path, manifestPath, widget.fileKey());
|
||||
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());
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
|
@ -177,7 +183,7 @@ class FileBubbleState extends State<FileBubble> {
|
|||
|
||||
void _btnResume() async {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
var handle = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||
var handle = Provider.of<MessageMetadata>(context, listen: false).conversationIdentifier;
|
||||
Provider.of<ProfileInfoState>(context, listen: false).downloadMarkResumed(widget.fileKey());
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.VerifyOrResumeDownload(profileOnion, handle, widget.fileKey());
|
||||
}
|
||||
|
|
|
@ -36,16 +36,16 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
Widget build(BuildContext context) {
|
||||
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||
var isGroup = widget.overlay == InviteGroupOverlay;
|
||||
isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(widget.inviteTarget) != null;
|
||||
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...
|
||||
var senderDisplayStr = "";
|
||||
if (!fromMe) {
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
if (contact != null) {
|
||||
senderDisplayStr = contact.nickname;
|
||||
} else {
|
||||
|
@ -69,7 +69,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning)
|
||||
: fromMe
|
||||
? senderInviteChrome(
|
||||
AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of<ProfileInfoState>(context).contactList.getContact(widget.inviteTarget)!.nickname : widget.inviteTarget)
|
||||
AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of<ProfileInfoState>(context).contactList.findContact(widget.inviteTarget)!.nickname : widget.inviteTarget)
|
||||
: (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, widget.inviteNick, widget.inviteTarget));
|
||||
|
||||
Widget wdgDecorations;
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
// If the sender is not us, then we want to give them a nickname...
|
||||
var senderDisplayStr = "";
|
||||
if (!fromMe) {
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
if (contact != null) {
|
||||
senderDisplayStr = contact.nickname;
|
||||
} else {
|
||||
|
@ -134,8 +134,7 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"Opening this link will launch an application outside of Cwtch and may reveal metadata or otherwise compromise the security of Cwtch. Only open links from people you trust. Are you sure you want to continue?"
|
||||
),
|
||||
"Opening this link will launch an application outside of Cwtch and may reveal metadata or otherwise compromise the security of Cwtch. Only open links from people you trust. Are you sure you want to continue?"),
|
||||
erinn
commented
i18n i18n
|
||||
Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||
|
@ -170,6 +169,6 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
],
|
||||
)),
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,18 +76,18 @@ class _MessageListState extends State<MessageList> {
|
|||
reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
|
||||
itemBuilder: (itemBuilderContext, index) {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(outerContext, listen: false).onion;
|
||||
var contactHandle = Provider.of<ContactInfoState>(outerContext, listen: false).onion;
|
||||
var messageIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
||||
var contactHandle = Provider.of<ContactInfoState>(outerContext, listen: false).identifier;
|
||||
var messageIndex = index;
|
||||
|
||||
return FutureBuilder(
|
||||
future: messageHandler(outerContext, profileOnion, contactHandle, messageIndex),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var message = snapshot.data as Message;
|
||||
// Already includes MessageRow,,
|
||||
return message.getWidget(context);
|
||||
var key = Provider.of<ContactInfoState>(outerContext, listen: false).getMessageKey(contactHandle, message.getMetadata().messageID);
|
||||
return message.getWidget(context, key);
|
||||
} else {
|
||||
return Text(''); //MessageLoadingBubble();
|
||||
return MessageLoadingBubble();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../model.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
|
||||
class MessageLoadingBubble extends StatefulWidget {
|
||||
@override
|
||||
|
|
|
@ -15,6 +15,7 @@ import '../settings.dart';
|
|||
|
||||
class MessageRow extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
MessageRow(this.child, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -28,9 +29,12 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
late Alignment _dragAlignment = Alignment.center;
|
||||
Alignment _dragAffinity = Alignment.center;
|
||||
|
||||
late int index;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
index = Provider.of<MessageMetadata>(context, listen: false).messageID;
|
||||
_controller = AnimationController(vsync: this);
|
||||
_controller.addListener(() {
|
||||
setState(() {
|
||||
|
@ -41,15 +45,17 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
if (_controller != null) {
|
||||
_controller.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||
var isContact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle) != null;
|
||||
var isBlocked = isContact ? Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle)!.isBlocked : false;
|
||||
var isContact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle) != null;
|
||||
var isBlocked = isContact ? Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle)!.isBlocked : false;
|
||||
var actualMessage = Flexible(flex: 3, fit: FlexFit.loose, child: widget.child);
|
||||
|
||||
_dragAffinity = fromMe ? Alignment.centerRight : Alignment.centerLeft;
|
||||
|
@ -60,7 +66,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
|
||||
var senderDisplayStr = "";
|
||||
if (!fromMe) {
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
if (contact != null) {
|
||||
senderDisplayStr = contact.nickname;
|
||||
} else {
|
||||
|
@ -69,7 +75,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
}
|
||||
|
||||
Widget wdgIcons = Visibility(
|
||||
visible: Provider.of<AppState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageIndex,
|
||||
visible: Provider.of<AppState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID,
|
||||
maintainSize: true,
|
||||
maintainAnimation: true,
|
||||
maintainState: true,
|
||||
|
@ -77,7 +83,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
child: IconButton(
|
||||
tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage,
|
||||
onPressed: () {
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageID;
|
||||
},
|
||||
icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor())));
|
||||
Widget wdgSpacer = Flexible(child: SizedBox(width: 60, height: 10));
|
||||
|
@ -163,7 +169,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
// For desktop...
|
||||
onHover: (event) {
|
||||
setState(() {
|
||||
Provider.of<AppState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
Provider.of<AppState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageID;
|
||||
});
|
||||
},
|
||||
onExit: (event) {
|
||||
|
@ -185,7 +191,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
},
|
||||
onPanEnd: (details) {
|
||||
_runAnimation(details.velocity.pixelsPerSecond, size);
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageID;
|
||||
},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(2),
|
||||
|
@ -198,8 +204,10 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
children: widgetRow,
|
||||
)))));
|
||||
var mark = Provider.of<ContactInfoState>(context).newMarker;
|
||||
if (mark > 0 && mark == Provider.of<ContactInfoState>(context).totalMessages - Provider.of<MessageMetadata>(context).messageIndex) {
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Align(alignment:Alignment.center ,child:_bubbleNew()), mr]);
|
||||
if (mark > 0 &&
|
||||
Provider.of<ContactInfoState>(context).messageCache.length > mark &&
|
||||
Provider.of<ContactInfoState>(context).messageCache[mark - 1]?.metadata.messageID == Provider.of<MessageMetadata>(context).messageID) {
|
||||
return Column(crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [Align(alignment: Alignment.center, child: _bubbleNew()), mr]);
|
||||
} else {
|
||||
return mr;
|
||||
}
|
||||
|
@ -209,9 +217,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||
border: Border.all(
|
||||
color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||
width: 1),
|
||||
border: Border.all(color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(), width: 1),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
|
@ -219,9 +225,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
bottomRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(9.0),
|
||||
child: Text(AppLocalizations.of(context)!.newMessagesLabel)));
|
||||
child: Padding(padding: EdgeInsets.all(9.0), child: Text(AppLocalizations.of(context)!.newMessagesLabel)));
|
||||
}
|
||||
|
||||
void _runAnimation(Offset pixelsPerSecond, Size size) {
|
||||
|
@ -249,12 +253,17 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
}
|
||||
|
||||
void _btnGoto() {
|
||||
selectConversation(context, Provider.of<MessageMetadata>(context, listen: false).senderHandle);
|
||||
var id = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context, listen: false).senderHandle)?.identifier;
|
||||
if (id == null) {
|
||||
// Can't happen
|
||||
} else {
|
||||
selectConversation(context, id);
|
||||
}
|
||||
}
|
||||
|
||||
void _btnAdd() {
|
||||
var sender = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||
if (sender == null || sender == "") {
|
||||
if (sender == "") {
|
||||
print("sender not yet loaded");
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
|
|||
// If the sender is not us, then we want to give them a nickname...
|
||||
var senderDisplayStr = "";
|
||||
if (!fromMe) {
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
if (contact != null) {
|
||||
senderDisplayStr = contact.nickname;
|
||||
} else {
|
||||
|
|
|
@ -21,40 +21,38 @@ class _ServerRowState extends State<ServerRow> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var server = Provider.of<ServerInfoState>(context);
|
||||
return Card(clipBehavior: Clip.antiAlias,
|
||||
return Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
margin: EdgeInsets.all(0.0),
|
||||
child: InkWell(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0), //border size
|
||||
child: Icon(CwtchIcons.dns_24px,
|
||||
color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(),
|
||||
size: 64)
|
||||
|
||||
),
|
||||
color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(), size: 64)),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
server.description,
|
||||
semanticsLabel: server.description,
|
||||
style: Provider.of<FlwtchState>(context).biggerFont.apply(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
||||
children: [
|
||||
Text(
|
||||
server.description,
|
||||
semanticsLabel: server.description,
|
||||
style: Provider.of<FlwtchState>(context)
|
||||
.biggerFont
|
||||
.apply(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Visibility(
|
||||
visible: !Provider.of<Settings>(context).streamerMode,
|
||||
child: ExcludeSemantics(
|
||||
child: Text(
|
||||
server.onion,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Visibility(
|
||||
visible: !Provider.of<Settings>(context).streamerMode,
|
||||
child: ExcludeSemantics(
|
||||
child: Text(
|
||||
server.onion,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
||||
)))
|
||||
],
|
||||
)),
|
||||
style: TextStyle(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
||||
)))
|
||||
],
|
||||
)),
|
||||
|
||||
// Copy server button
|
||||
IconButton(
|
||||
|
@ -75,20 +73,20 @@ class _ServerRowState extends State<ServerRow> {
|
|||
_pushEditServer(server);
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
])));
|
||||
}
|
||||
|
||||
void _pushEditServer(ServerInfoState server ) {
|
||||
void _pushEditServer(ServerInfoState server) {
|
||||
Provider.of<ErrorHandler>(context).reset();
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
settings: RouteSettings(name: "serveraddedit"),
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [ChangeNotifierProvider<ServerInfoState>(
|
||||
create: (_) => server,
|
||||
)],
|
||||
providers: [
|
||||
ChangeNotifierProvider<ServerInfoState>(
|
||||
create: (_) => server,
|
||||
)
|
||||
],
|
||||
child: AddEditServerView(),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
|
@ -417,7 +417,7 @@ packages:
|
|||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.3"
|
||||
version: "0.4.7"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -473,7 +473,7 @@ packages:
|
|||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
Loading…
Reference in New Issue
?