diff --git a/LIBCWTCH-GO-MACOS.version b/LIBCWTCH-GO-MACOS.version
index 462defe0..3c6d2eca 100644
--- a/LIBCWTCH-GO-MACOS.version
+++ b/LIBCWTCH-GO-MACOS.version
@@ -1 +1 @@
-2021-11-09-18-25-v1.4.2
\ No newline at end of file
+2021-12-08-00-32-v1.5.0-7-g28a13aa
\ No newline at end of file
diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version
index 9246eb36..33bf0526 100644
--- a/LIBCWTCH-GO.version
+++ b/LIBCWTCH-GO.version
@@ -1 +1 @@
-2021-11-09-23-25-v1.4.2
\ No newline at end of file
+2021-12-08-05-32-v1.5.0-7-g28a13aa
\ No newline at end of file
diff --git a/README.md b/README.md
index 29280794..755398a4 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Cwtch processes the following environment variables:
First you will need a valid [flutter sdk installation](https://flutter.dev/docs/get-started/install).
You will probably want to disable Analytics on the Flutter Tool: `flutter config --no-analytics`
-This project uses the flutter `dev` channel, which you will need to switch to: `flutter channel dev; flutter upgrade`.
+This project uses the flutter `stable` channel
Once flutter is set up, run `flutter pub get` from this project folder to fetch dependencies.
@@ -42,17 +42,20 @@ To build a release version and load normal profiles, use `build-release.sh X` in
- set `LD_LIBRARY_PATH="$PWD/linux"`
- copy a `tor` binary to `linux/` or run `fetch-tor.sh` to download one
- run `flutter config --enable-linux-desktop` if you've never done so before
-- optional: launch cwtch-ui directly by running `flutter run -d linux`
+- optional: launch cwtch-ui debug build by running `flutter run -d linux`
- to build cwtch-ui, run `flutter build linux`
-- optional: launch cwtch-ui build with `env LD_LIBRARY_PATH=linux ./build/linux/x64/release/bundle/cwtch`
+- optional: launch cwtch-ui release build with `env LD_LIBRARY_PATH=linux ./build/linux/x64/release/bundle/cwtch`
- to package the build, run `linux/package-release.sh`
### Building on Windows (for Windows)
- copy `libCwtch.dll` to `windows/`, or run `fetch-libcwtch-go.ps1` to download it
- run `fetch-tor-win.ps1` to fetch Tor for windows
-- optional: launch cwtch-ui directly by running `flutter run -d windows`
+- optional: launch cwtch-ui debug build by running `flutter run -d windows`
- to build cwtch-ui, run `flutter build windows`
+- optional: to run the release build:
+ - `cp windows/libCwtch.dll .`
+ - `./build/windows/runner/Release/cwtch.exe`
### Building on Linux/Windows (for Android)
@@ -65,10 +68,8 @@ To build a release version and load normal profiles, use `build-release.sh X` in
- copy `libCwtch.dylib` into the root folder, or run `fetch-libcwtch-go-macos.sh` to download it
- run `fetch-tor-macos.sh` to fetch Tor or Download and install Tor Browser and `cp -r /Applications/Tor\ Browser.app/Contents/MacOS/Tor ./macos/`
- `flutter build macos`
-- optional: launch cwtch-ui build with `./build/linux/x64/release/bundle/cwtch`
-- `./macos/package-release.sh`
-
-results in a Cwtch.dmg that has libCwtch.dylib and tor in it as well and can be installed into Applications
+- optional: launch cwtch-ui release build with `./build/macos/Build/Products/Release/Cwtch.app/Contents/MacOS/Cwtch`
+- To package the UI: `./macos/package-release.sh`, which results in a Cwtch.dmg that has libCwtch.dylib and tor in it as well and can be installed into Applications
### Known Platform Issues
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ec51df3e..ff6890c9 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -48,4 +48,11 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt
index c82d44ff..e8c737ad 100644
--- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt
+++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt
@@ -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()
}
diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt
index d0f6503d..20940c89 100644
--- a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt
+++ b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt
@@ -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()
diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart
index c7efdbf3..a2606b27 100644
--- a/lib/cwtch/cwtch.dart
+++ b/lib/cwtch/cwtch.dart
@@ -10,8 +10,6 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names
Future 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 GetMessage(String profile, String handle, int index);
- // ignore: non_constant_identifier_names
- Future 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 GetMessage(String profile, int handle, int index);
// ignore: non_constant_identifier_names
- void ShareFile(String profile, String handle, String filepath);
+ Future 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 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
diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart
index 7f16ab49..d3c01c55 100644
--- a/lib/cwtch/cwtchNotifier.dart
+++ b/lib/cwtch/cwtchNotifier.dart
@@ -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");
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(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(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(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(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");
}
diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart
index 87eb66d1..37ea80a8 100644
--- a/lib/cwtch/ffi.dart
+++ b/lib/cwtch/ffi.dart
@@ -36,8 +36,9 @@ typedef VoidFromStringStringStringFn = void Function(Pointer, int, Pointer
typedef void_from_string_string_string_string_function = Void Function(Pointer, Int32, Pointer, Int32, Pointer, Int32, Pointer, Int32);
typedef VoidFromStringStringStringStringFn = void Function(Pointer, int, Pointer, int, Pointer, int, Pointer, int);
-typedef void_from_string_string_string_string_string_function = Void Function(Pointer, Int32, Pointer, Int32, Pointer, Int32, Pointer, Int32, Pointer, Int32);
-typedef VoidFromStringStringStringStringStringFn = void Function(Pointer, int, Pointer, int, Pointer, int, Pointer, int, Pointer, int);
+// DownloadFile
+typedef void_from_string_int_string_string_string_function = Void Function(Pointer, Int32, Int32, Pointer, Int32, Pointer, Int32, Pointer, Int32);
+typedef VoidFromStringIntStringStringStringFn = void Function(Pointer, int, int, Pointer, int, Pointer, int, Pointer, int);
typedef void_from_string_string_int_int_function = Void Function(Pointer, Int32, Pointer, Int32, Int64, Int64);
typedef VoidFromStringStringIntIntFn = void Function(Pointer, int, Pointer, int, int, int);
@@ -51,6 +52,9 @@ typedef StringFn = void Function(Pointer dir, int);
typedef string_string_to_void_function = Void Function(Pointer str, Int32 length, Pointer str2, Int32 length2);
typedef StringStringFn = void Function(Pointer, int, Pointer, int);
+typedef string_int_to_void_function = Void Function(Pointer str, Int32 length, Int32 handle);
+typedef VoidFromStringIntFn = void Function(Pointer, int, int);
+
typedef get_json_blob_string_function = Pointer Function(Pointer str, Int32 length);
typedef GetJsonBlobStringFn = Pointer Function(Pointer str, int len);
@@ -58,10 +62,34 @@ typedef GetJsonBlobStringFn = Pointer Function(Pointer str, int len)
typedef get_json_blob_from_str_str_int_function = Pointer Function(Pointer, Int32, Pointer, Int32, Int32);
typedef GetJsonBlobFromStrStrIntFn = Pointer Function(Pointer, int, Pointer, int, int);
+typedef get_json_blob_from_str_int_int_function = Pointer Function(Pointer, Int32, Int32, Int32);
+typedef GetJsonBlobFromStrIntIntFn = Pointer Function(Pointer, int, int, int);
+
+typedef get_json_blob_from_str_int_string_function = Pointer Function(Pointer, Int32, Int32, Pointer, Int32);
+typedef GetJsonBlobFromStrIntStringFn = Pointer Function(
+ Pointer,
+ int,
+ int,
+ Pointer,
+ 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 Function(Pointer, Int32, Pointer, Int32, Pointer, Int32);
typedef GetJsonBlobFromStrStrStrFn = Pointer Function(Pointer, int, Pointer, int, Pointer, int);
+typedef void_from_string_int_string_function = Void Function(Pointer, Int32, Int32, Pointer, Int32);
+typedef VoidFromStringIntStringFn = void Function(Pointer, int, int, Pointer, int);
+
+typedef void_from_string_int_string_string_function = Void Function(Pointer, Int32, Int32, Pointer, Int32, Pointer, Int32);
+typedef VoidFromStringIntStringStringFn = void Function(Pointer, int, int, Pointer, int, Pointer, int);
+
+typedef void_from_string_int_int_int_string_string_function = Void Function(Pointer, Int32, Int32, Int32, Int32, Pointer, Int32, Pointer, Int32);
+typedef VoidFromStringIntIntIntStringStringFn = void Function(Pointer, int, int, int, int, Pointer, int, Pointer, int);
+
+typedef void_from_string_int_int_function = Void Function(Pointer, Int32, Int32, Int32);
+typedef VoidFromStringIntIntFn = void Function(Pointer, int, int, int);
+
typedef appbus_events_function = Pointer Function();
typedef AppbusEventsFn = Pointer Function();
@@ -233,16 +261,6 @@ class CwtchFfi implements Cwtch {
}
}
- // ignore: non_constant_identifier_names
- void SelectProfile(String onion) async {
- var selectProfileC = library.lookup>("c_SelectProfile");
- // ignore: non_constant_identifier_names
- final SelectProfile = selectProfileC.asFunction();
- 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>("c_CreateProfile");
@@ -266,17 +284,15 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
- Future GetMessage(String profile, String handle, int index) async {
- var getMessageC = library.lookup>("c_GetMessage");
+ Future GetMessage(String profile, int handle, int index) async {
+ var getMessageC = library.lookup>("c_GetMessage");
// ignore: non_constant_identifier_names
- final GetMessage = getMessageC.asFunction();
+ final GetMessage = getMessageC.asFunction();
final utf8profile = profile.toNativeUtf8();
- final utf8handle = handle.toNativeUtf8();
- Pointer jsonMessageBytes = GetMessage(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index);
+ Pointer 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>("c_AcceptContact");
+ void AcceptContact(String profileOnion, int contactHandle) {
+ var acceptContact = library.lookup>("c_AcceptConversation");
// ignore: non_constant_identifier_names
- final AcceptContact = acceptContact.asFunction();
+ final AcceptContact = acceptContact.asFunction();
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>("c_BlockContact");
+ void BlockContact(String profileOnion, int contactHandle) {
+ var blockContact = library.lookup>("c_BlockContact");
// ignore: non_constant_identifier_names
- final BlockContact = blockContact.asFunction();
+ final BlockContact = blockContact.asFunction();
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>("c_SendMessage");
+ void SendMessage(String profileOnion, int contactHandle, String message) {
+ var sendMessage = library.lookup>("c_SendMessage");
// ignore: non_constant_identifier_names
- final SendMessage = sendMessage.asFunction();
+ final SendMessage = sendMessage.asFunction();
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>("c_SendInvitation");
+ void SendInvitation(String profileOnion, int contactHandle, int target) {
+ var sendInvitation = library.lookup>("c_SendInvitation");
// ignore: non_constant_identifier_names
- final SendInvitation = sendInvitation.asFunction();
+ final SendInvitation = sendInvitation.asFunction();
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>("c_ShareFile");
+ void ShareFile(String profileOnion, int contactHandle, String filepath) {
+ var shareFile = library.lookup>("c_ShareFile");
// ignore: non_constant_identifier_names
- final ShareFile = shareFile.asFunction();
+ final ShareFile = shareFile.asFunction();
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>("c_DownloadFile");
+ void DownloadFile(String profileOnion, int contactHandle, String filepath, String manifestpath, String filekey) {
+ var dlFile = library.lookup>("c_DownloadFile");
// ignore: non_constant_identifier_names
- final DownloadFile = dlFile.asFunction();
+ final DownloadFile = dlFile.asFunction();
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>("c_VerifyOrResumeDownload");
+ void VerifyOrResumeDownload(String profileOnion, int contactHandle, String filekey) {
+ var fn = library.lookup>("c_VerifyOrResumeDownload");
// ignore: non_constant_identifier_names
- final VerifyOrResumeDownload = fn.asFunction();
+ final VerifyOrResumeDownload = fn.asFunction();
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>("c_SetGroupAttribute");
- // ignore: non_constant_identifier_names
- final SetGroupAttribute = setGroupAttribute.asFunction();
- 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>("c_RejectInvite");
- // ignore: non_constant_identifier_names
- final RejectInvite = rejectInvite.asFunction();
- 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>("c_ArchiveConversation");
+ void ArchiveConversation(String profileOnion, int handle) {
+ var archiveConversation = library.lookup>("c_ArchiveConversation");
// ignore: non_constant_identifier_names
- final ArchiveConversation = archiveConversation.asFunction();
+ final ArchiveConversation = archiveConversation.asFunction();
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>("c_DeleteContact");
+ void DeleteContact(String profileOnion, int handle) {
+ var deleteContact = library.lookup>("c_DeleteContact");
// ignore: non_constant_identifier_names
- final DeleteContact = deleteContact.asFunction();
+ final DeleteContact = deleteContact.asFunction();
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>("c_UpdateMessageFlags");
- // ignore: non_constant_identifier_names
- final updateMessageFlags = updateMessageFlagsC.asFunction();
- 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();
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>("c_SetContactAttribute");
+ void SetConversationAttribute(String profile, int contact, String key, String val) {
+ var setContactAttribute = library.lookup>("c_SetConversationAttribute");
// ignore: non_constant_identifier_names
- final SetContactAttribute = setContactAttribute.asFunction();
+ final SetContactAttribute = setContactAttribute.asFunction();
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>("c_SetMessageAttribute");
+ // ignore: non_constant_identifier_names
+ final SetMessageAttribute = setMessageAttribute.asFunction();
+ 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>("c_GetMessagesByContentHash");
+ Future GetMessageByContentHash(String profile, int handle, String contentHash) async {
+ var getMessagesByContentHashC = library.lookup>("c_GetMessagesByContentHash");
// ignore: non_constant_identifier_names
- final GetMessagesByContentHash = getMessagesByContentHashC.asFunction();
+ final GetMessagesByContentHash = getMessagesByContentHashC.asFunction();
final utf8profile = profile.toNativeUtf8();
- final utf8handle = handle.toNativeUtf8();
final utf8contentHash = contentHash.toNativeUtf8();
- Pointer jsonMessageBytes = GetMessagesByContentHash(utf8profile, utf8profile.length, utf8handle, utf8handle.length, utf8contentHash, utf8contentHash.length);
+ Pointer 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();
Free(ptr);
}
+
+ @override
+ Future GetMessageByID(String profile, int handle, int index) async {
+ var getMessageC = library.lookup>("c_GetMessageByID");
+ // ignore: non_constant_identifier_names
+ final GetMessage = getMessageC.asFunction();
+ final utf8profile = profile.toNativeUtf8();
+ Pointer jsonMessageBytes = GetMessage(utf8profile, utf8profile.length, handle, index);
+ String jsonMessage = jsonMessageBytes.toDartString();
+ _UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(jsonMessageBytes);
+ malloc.free(utf8profile);
+ return jsonMessage;
+ }
}
diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart
index ae30a80c..1b4c7fd0 100644
--- a/lib/cwtch/gomobile.dart
+++ b/lib/cwtch/gomobile.dart
@@ -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 GetMessage(String profile, String handle, int index) {
- print("gomobile.dart GetMessage " + index.toString());
- return cwtchPlatform.invokeMethod("GetMessage", {"profile": profile, "contact": handle, "index": index});
+ Future GetMessage(String profile, int conversation, int index) {
+ return cwtchPlatform.invokeMethod("GetMessage", {"ProfileOnion": profile, "conversation": conversation, "index": index});
+ }
+
+ // ignore: non_constant_identifier_names
+ Future 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 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});
}
}
diff --git a/lib/model.dart b/lib/model.dart
index 23780557..87ac2f36 100644
--- a/lib/model.dart
+++ b/lib/model.dart
@@ -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 {
//}
}
- 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 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 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> keys;
+ late List 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>();
}
@@ -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 getMessageKey(String index) {
+ GlobalKey getMessageKey(int conversation, int message) {
+ String index = "c: " + conversation.toString() + " m:" + message.toString();
if (keys[index] == null) {
keys[index] = GlobalKey();
}
GlobalKey ret = keys[index]!;
return ret;
}
+
+ GlobalKey? getMessageKeyOrFail(int conversation, int message) {
+ String index = "c: " + conversation.toString() + " m:" + message.toString();
+
+ if (keys[index] == null) {
+ return null;
+ }
+ GlobalKey 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() {
+ this.messageCache.insert(0, null);
+ this.totalMessages += 1;
+ }
+
+ void ackCache(int messageID) {
+ this.messageCache.firstWhere((element) => element?.metadata.messageID == messageID)?.metadata.ackd = true;
+ notifyListeners();
+ }
}
diff --git a/lib/models/message.dart b/lib/models/message.dart
index 2156b8d6..4df9eace 100644
--- a/lib/models/message.dart
+++ b/lib/models/message.dart
@@ -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 messageHandler(BuildContext context, String profileOnion, String contactHandle, int index) {
+Message compileOverlay(MessageMetadata metadata, String messageData) {
try {
- var rawMessageEnvelopeFuture = Provider.of(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 messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, int index, {bool byID = false}) {
+ var cache = Provider.of(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 rawMessageEnvelopeFuture;
+
+ if (byID) {
+ rawMessageEnvelopeFuture = Provider.of(context, listen: false).cwtch.GetMessageByID(profileOnion, conversationIdentifier, index);
+ } else {
+ rawMessageEnvelopeFuture = Provider.of(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(), "", "", "", {}, false, true);
try {
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
// There are 2 conditions in which this error condition can be met:
@@ -50,72 +90,48 @@ Future 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(), "", "", "", {}, 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);
}
diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart
index 0ea5cbc7..9254c6d2 100644
--- a/lib/models/messages/filemessage.dart
+++ b/lib/models/messages/filemessage.dart
@@ -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(bcontext).getMessageKey(idx));
+ return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize), key: key);
});
}
diff --git a/lib/models/messages/invitemessage.dart b/lib/models/messages/invitemessage.dart
index df21e313..149ba5e0 100644
--- a/lib/models/messages/invitemessage.dart
+++ b/lib/models/messages/invitemessage.dart
@@ -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(context).contactList.getContact(inviteTarget);
+ var targetContact = Provider.of(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(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(context).contactList.getContact(inviteTarget);
+ var targetContact = Provider.of(context).contactList.findContact(inviteTarget);
inviteNick = targetContact == null ? this.content : targetContact.nickname;
} else {
var parts = this.content.toString().split("||");
diff --git a/lib/models/messages/malformedmessage.dart b/lib/models/messages/malformedmessage.dart
index dc943fe1..0ebf2281 100644
--- a/lib/models/messages/malformedmessage.dart
+++ b/lib/models/messages/malformedmessage.dart
@@ -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);
});
}
diff --git a/lib/models/messages/quotedmessage.dart b/lib/models/messages/quotedmessage.dart
index 8bc0c905..5a69ca91 100644
--- a/lib/models/messages/quotedmessage.dart
+++ b/lib/models/messages/quotedmessage.dart
@@ -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(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.contactHandle, message["quotedHash"]);
- int messageIndex = metadata.messageIndex;
+ var quotedMessagePotentials = Provider.of(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.conversationIdentifier, message["quotedHash"]);
Future quotedMessage = quotedMessagePotentials.then((matchingMessages) {
if (matchingMessages == "[]") {
return null;
@@ -81,9 +80,7 @@ class QuotedMessage extends Message {
// message
try {
var list = (jsonDecode(matchingMessages) as List).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(bcontext).getMessageKey(idx));
+ key: key);
});
} catch (e) {
- return MalformedMessage(this.metadata).getWidget(context);
+ return MalformedMessage(this.metadata).getWidget(context, key);
}
}
}
diff --git a/lib/models/messages/textmessage.dart b/lib/models/messages/textmessage.dart
index a5782ac4..a8d7f6af 100644
--- a/lib/models/messages/textmessage.dart
+++ b/lib/models/messages/textmessage.dart
@@ -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(bcontext).getMessageKey(idx));
+ return MessageRow(
+ MessageBubble(this.content),
+ key: key,
+ );
});
}
}
diff --git a/lib/models/servers.dart b/lib/models/servers.dart
index bf9710dd..2850668b 100644
--- a/lib/models/servers.dart
+++ b/lib/models/servers.dart
@@ -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");
}
diff --git a/lib/settings.dart b/lib/settings.dart
index 436e7d88..7397ce39 100644
--- a/lib/settings.dart
+++ b/lib/settings.dart
@@ -11,6 +11,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
const TapirGroupsExperiment = "tapir-groups-experiment";
const ServerManagementExperiment = "servers-experiment";
const FileSharingExperiment = "filesharing";
+const ClickableLinksExperiment = "clickable-links";
enum DualpaneMode {
Single,
diff --git a/lib/views/addeditprofileview.dart b/lib/views/addeditprofileview.dart
index 6b8f1e7c..2355fd57 100644
--- a/lib/views/addeditprofileview.dart
+++ b/lib/views/addeditprofileview.dart
@@ -296,11 +296,13 @@ class _AddEditProfileViewState extends State {
// Profile Editing
if (ctrlrPass.value.text.isEmpty) {
// Don't update password, only update name
+ Provider.of(context, listen: false).nickname = ctrlrNick.value.text;
Provider.of(context, listen: false).cwtch.SetProfileAttribute(Provider.of(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(context, listen: false).nickname = ctrlrNick.value.text;
Provider.of(context, listen: false).cwtch.SetProfileAttribute(Provider.of(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
final updatePasswordEvent = {
"EventType": "ChangePassword",
diff --git a/lib/views/addeditservers.dart b/lib/views/addeditservers.dart
index c551ea4d..2a1c8f46 100644
--- a/lib/views/addeditservers.dart
+++ b/lib/views/addeditservers.dart
@@ -53,7 +53,6 @@ class _AddEditServerViewState extends State {
@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 {
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(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
+ } else {
+ Provider.of(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(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
- } else {
- Provider.of(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(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(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: [
+ 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: [
+ 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(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: [
- 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: [
- 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(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 {
// match (and are provided if the user has requested an encrypted profile).
if (_formKey.currentState!.validate()) {
if (usePassword) {
- Provider
- .of(context, listen: false)
- .cwtch
- .CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of(context, listen: false).autoStart);
+ Provider.of(context, listen: false).cwtch.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of(context, listen: false).autoStart);
} else {
- Provider
- .of(context, listen: false)
- .cwtch
- .CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of(context, listen: false).autoStart);
+ Provider.of(context, listen: false).cwtch.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of(context, listen: false).autoStart);
}
Navigator.of(context).pop();
}
}
void _savePressed() {
-
var server = Provider.of(context, listen: false);
- Provider.of(context, listen: false)
- .cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text);
+ Provider.of(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 {
Widget continueButton = ElevatedButton(
child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
onPressed: () {
- var onion = Provider
- .of(context, listen: false)
- .onion;
- Provider
- .of(context, listen: false)
- .cwtch
- .DeleteServer(onion, Provider.of(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword);
+ var onion = Provider.of(context, listen: false).onion;
+ Provider.of(context, listen: false).cwtch.DeleteServer(onion, Provider.of(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);
@@ -395,4 +370,4 @@ class _AddEditServerViewState extends State {
},
);
}
-}
\ No newline at end of file
+}
diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart
index 895b06ac..990899f3 100644
--- a/lib/views/contactsview.dart
+++ b/lib/views/contactsview.dart
@@ -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(context, listen: false).contactList.getContact(handle)!.unreadMessages;
Provider.of(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0;
@@ -36,7 +36,7 @@ void selectConversation(BuildContext context, String handle) {
if (Provider.of(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(context, listen: false).onion;
Navigator.of(context).push(
MaterialPageRoute(
@@ -112,7 +112,6 @@ class _ContactsViewState extends State {
Clipboard.setData(new ClipboardData(text: Provider.of(context, listen: false).onion));
}));
-
// TODO servers
// Search contacts
diff --git a/lib/views/doublecolview.dart b/lib/views/doublecolview.dart
index 99d2aa9f..329821c7 100644
--- a/lib/views/doublecolview.dart
+++ b/lib/views/doublecolview.dart
@@ -34,8 +34,8 @@ class _DoubleColumnViewState extends State