From 1d0cb785c1652a86769e8a3df7dcd358d83a758e Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 3 Mar 2022 17:00:36 -0800 Subject: [PATCH 1/2] fix android segfault in flwtch worker; try/catch to catch future bugs in flwtch worker; resume servers load sync status from cwtch; add bg color to sync progress bar; showdown now synchronous so completes --- LIBCWTCH-GO-MACOS.version | 2 +- LIBCWTCH-GO.version | 2 +- .../kotlin/im/cwtch/flwtch/FlwtchWorker.kt | 582 +++++++++--------- lib/cwtch/cwtch.dart | 2 +- lib/cwtch/cwtchNotifier.dart | 2 +- lib/main.dart | 4 +- lib/models/profile.dart | 4 +- lib/models/remoteserver.dart | 7 +- lib/notification_manager.dart | 2 +- lib/widgets/contactrow.dart | 1 + lib/widgets/remoteserverrow.dart | 1 + pubspec.yaml | 2 +- 12 files changed, 314 insertions(+), 297 deletions(-) diff --git a/LIBCWTCH-GO-MACOS.version b/LIBCWTCH-GO-MACOS.version index 26f7fbe3..20c15581 100644 --- a/LIBCWTCH-GO-MACOS.version +++ b/LIBCWTCH-GO-MACOS.version @@ -1 +1 @@ -2022-02-23-17-17-v1.6.0-2-ge8b2def \ No newline at end of file +2022-03-03-19-53-v1.6.0-4-g4b881b9 \ No newline at end of file diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index 344ffca9..aa80e80c 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -2022-02-23-22-17-v1.6.0-2-ge8b2def \ No newline at end of file +2022-03-04-00-54-v1.6.0-4-g4b881b9 \ No newline at end of file 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 f1664f78..38ede391 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -38,7 +38,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val args = inputData.getString(KEY_ARGS) ?: return Result.failure() // Mark the Worker as important - val progress = "Cwtch is keeping Tor running in the background"//todo:translate + val progress = "Cwtch is keeping Tor running in the background" // TODO: translate setForeground(createForegroundInfo(progress)) return handleCwtch(method, args) } @@ -52,25 +52,27 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : } private fun handleCwtch(method: String, args: String): Result { - val a = JSONObject(args) - when (method) { - "Start" -> { - Log.i("FlwtchWorker.kt", "handleAppInfo Start") - val appDir = (a.get("appDir") as? String) ?: "" - val torPath = (a.get("torPath") as? String) ?: "tor" - Log.i("FlwtchWorker.kt", "appDir: '$appDir' torPath: '$torPath'") + try { + val a = JSONObject(args) + when (method) { + "Start" -> { + Log.i("FlwtchWorker.kt", "handleAppInfo Start") + val appDir = (a.get("appDir") as? String) ?: "" + val torPath = (a.get("torPath") as? String) ?: "tor" + Log.i("FlwtchWorker.kt", "appDir: '$appDir' torPath: '$torPath'") - if (Cwtch.startCwtch(appDir, torPath) != 0.toLong()) return Result.failure() + if (Cwtch.startCwtch(appDir, torPath) != 0.toLong()) return Result.failure() - Log.i("FlwtchWorker.kt", "startCwtch success, starting coroutine AppbusEvent loop...") - val downloadIDs = mutableMapOf() - while(true) { - val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent()) - if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") { - val data = JSONObject(evt.Data) - val handle = if (evt.EventType == "NewMessageFromPeer") data.getString("RemotePeer") else data.getString("GroupID"); - if (data["RemotePeer"] != data["ProfileOnion"]) { - val notification = data["notification"] + Log.i("FlwtchWorker.kt", "startCwtch success, starting coroutine AppbusEvent loop...") + val downloadIDs = mutableMapOf() + while (true) { + val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent()) + // TODO replace this notification block with the NixNotification manager in dart as it has access to contact names and also needs less working around + if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") { + val data = JSONObject(evt.Data) + val handle = data.getString("RemotePeer"); + if (data["RemotePeer"] != data["ProfileOnion"]) { + val notification = data["notification"] if (notification == "SimpleEvent") { val channelId = @@ -118,7 +120,8 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val newNotification = NotificationCompat.Builder(applicationContext, channelId) .setContentTitle(data.getString("Nick")) - .setContentText((notificationConversationInfo ?: "New Message From %1").replace("%1", data.getString("Nick"))) + .setContentText((notificationConversationInfo + ?: "New Message From %1").replace("%1", data.getString("Nick"))) .setLargeIcon(BitmapFactory.decodeStream(fh)) .setSmallIcon(R.mipmap.knott_transparent) .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) @@ -128,291 +131,296 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification) } - } - } else if (evt.EventType == "FileDownloadProgressUpdate") { - try { + } + } else if (evt.EventType == "FileDownloadProgressUpdate") { + try { + val data = JSONObject(evt.Data); + val fileKey = data.getString("FileKey"); + val title = data.getString("NameSuggestion"); + val progress = data.getString("Progress").toInt(); + val progressMax = data.getString("FileSizeInChunks").toInt(); + if (!downloadIDs.containsKey(fileKey)) { + downloadIDs.put(fileKey, downloadIDs.count()); + } + var dlID = downloadIDs.get(fileKey); + if (dlID == null) { + dlID = 0; + } + if (progress >= 0) { + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createDownloadNotificationChannel(fileKey, fileKey) + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + }; + val newNotification = NotificationCompat.Builder(applicationContext, channelId) + .setOngoing(true) + .setContentTitle("Downloading")//todo: translate + .setContentText(title) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setProgress(progressMax, progress, false) + .setSound(null) + //.setSilent(true) + .build(); + notificationManager.notify(dlID, newNotification); + } + } catch (e: Exception) { + Log.d("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace()); + } + } else if (evt.EventType == "FileDownloaded") { + Log.d("FlwtchWorker", "file downloaded!"); val data = JSONObject(evt.Data); + val tempFile = data.getString("TempFile"); val fileKey = data.getString("FileKey"); - val title = data.getString("NameSuggestion"); - val progress = data.getString("Progress").toInt(); - val progressMax = data.getString("FileSizeInChunks").toInt(); - if (!downloadIDs.containsKey(fileKey)) { - downloadIDs.put(fileKey, downloadIDs.count()); + if (tempFile != "" && tempFile != data.getString("FilePath")) { + val filePath = data.getString("FilePath"); + Log.i("FlwtchWorker", "moving " + tempFile + " to " + filePath); + val sourcePath = Paths.get(tempFile); + val targetUri = Uri.parse(filePath); + val os = this.applicationContext.getContentResolver().openOutputStream(targetUri); + val bytesWritten = Files.copy(sourcePath, os); + Log.d("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes"); + if (bytesWritten != 0L) { + os?.flush(); + os?.close(); + Files.delete(sourcePath); + } } - var dlID = downloadIDs.get(fileKey); - if (dlID == null) { - dlID = 0; - } - if (progress >= 0) { - val channelId = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createDownloadNotificationChannel(fileKey, fileKey) - } else { - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - "" - }; - val newNotification = NotificationCompat.Builder(applicationContext, channelId) - .setOngoing(true) - .setContentTitle("Downloading")//todo: translate - .setContentText(title) - .setSmallIcon(android.R.drawable.stat_sys_download) - .setProgress(progressMax, progress, false) - .setSound(null) - //.setSilent(true) - .build(); - notificationManager.notify(dlID, newNotification); - } - } catch (e: Exception) { - Log.d("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace()); - } - } else if (evt.EventType == "FileDownloaded") { - Log.d("FlwtchWorker", "file downloaded!"); - val data = JSONObject(evt.Data); - val tempFile = data.getString("TempFile"); - val fileKey = data.getString("FileKey"); - if (tempFile != "" && tempFile != data.getString("FilePath")) { - val filePath = data.getString("FilePath"); - Log.i("FlwtchWorker", "moving "+tempFile+" to "+filePath); - val sourcePath = Paths.get(tempFile); - val targetUri = Uri.parse(filePath); - val os = this.applicationContext.getContentResolver().openOutputStream(targetUri); - val bytesWritten = Files.copy(sourcePath, os); - Log.d("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes"); - if (bytesWritten != 0L) { - os?.flush(); - os?.close(); - Files.delete(sourcePath); + if (downloadIDs.containsKey(fileKey)) { + notificationManager.cancel(downloadIDs.get(fileKey) ?: 0); } } - if (downloadIDs.containsKey(fileKey)) { - notificationManager.cancel(downloadIDs.get(fileKey)?:0); - } - } - Intent().also { intent -> - intent.action = "im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS" - intent.putExtra("EventType", evt.EventType) - intent.putExtra("Data", evt.Data) - intent.putExtra("EventID", evt.EventID) - LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent) + Intent().also { intent -> + intent.action = "im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS" + intent.putExtra("EventType", evt.EventType) + intent.putExtra("Data", evt.Data) + intent.putExtra("EventID", evt.EventID) + LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent) + } } } - } - "ReconnectCwtchForeground" -> { - Cwtch.reconnectCwtchForeground() - } - "CreateProfile" -> { - val nick = (a.get("nick") as? String) ?: "" - val pass = (a.get("pass") as? String) ?: "" - Cwtch.createProfile(nick, pass) - } - "LoadProfiles" -> { - val pass = (a.get("pass") as? String) ?: "" - Cwtch.loadProfiles(pass) - } - "ChangePassword" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val pass = (a.get("OldPass") as? String) ?: "" - val passNew = (a.get("NewPass") as? String) ?: "" - val passNew2 = (a.get("NewPassAgain") as? String) ?: "" - Cwtch.changePassword(profile, pass, passNew, passNew2) - } - "GetMessage" -> { - 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("ProfileOnion") as? String) ?: "" - 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 conversation = a.getInt("conversation").toLong() - Cwtch.blockContact(profile, conversation) - } - "UnblockContact" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val conversation = a.getInt("conversation").toLong() - Cwtch.unblockContact(profile, conversation) - } - "SendMessage" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val conversation = a.getInt("conversation").toLong() - val message = (a.get("message") as? String) ?: "" - Cwtch.sendMessage(profile, conversation, message) - } - "SendInvitation" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val conversation = a.getInt("conversation").toLong() - val target = a.getInt("target").toLong() - Cwtch.sendInvitation(profile, conversation, target) - } - "ShareFile" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val conversation = a.getInt("conversation").toLong() - val filepath = (a.get("filepath") as? String) ?: "" - Cwtch.shareFile(profile, conversation, filepath) - } - "DownloadFile" -> { - val profile = (a.get("ProfileOnion") 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, conversation, filepath, manifestpath, filekey) + "ReconnectCwtchForeground" -> { + Cwtch.reconnectCwtchForeground() + } + "CreateProfile" -> { + val nick = (a.get("nick") as? String) ?: "" + val pass = (a.get("pass") as? String) ?: "" + Cwtch.createProfile(nick, pass) + } + "LoadProfiles" -> { + val pass = (a.get("pass") as? String) ?: "" + Cwtch.loadProfiles(pass) + } + "ChangePassword" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val pass = (a.get("OldPass") as? String) ?: "" + val passNew = (a.get("NewPass") as? String) ?: "" + val passNew2 = (a.get("NewPassAgain") as? String) ?: "" + Cwtch.changePassword(profile, pass, passNew, passNew2) + } + "GetMessage" -> { + 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("ProfileOnion") as? String) ?: "" + 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 conversation = a.getInt("conversation").toLong() + Cwtch.blockContact(profile, conversation) + } + "UnblockContact" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val conversation = a.getInt("conversation").toLong() + Cwtch.unblockContact(profile, conversation) + } + "SendMessage" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val conversation = a.getInt("conversation").toLong() + val message = (a.get("message") as? String) ?: "" + Log.i("FlwtchWorker.kt", "SendMessage: $message") + Cwtch.sendMessage(profile, conversation, message) + } + "SendInvitation" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val conversation = a.getInt("conversation").toLong() + val target = a.getInt("target").toLong() + Cwtch.sendInvitation(profile, conversation, target) + } + "ShareFile" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val conversation = a.getInt("conversation").toLong() + val filepath = (a.get("filepath") as? String) ?: "" + Cwtch.shareFile(profile, conversation, filepath) + } + "DownloadFile" -> { + val profile = (a.get("ProfileOnion") 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, conversation, filepath, manifestpath, filekey) + } + } + "CheckDownloadStatus" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val fileKey = (a.get("fileKey") as? String) ?: "" + Cwtch.checkDownloadStatus(profile, fileKey) + } + "VerifyOrResumeDownload" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val conversation = a.getInt("conversation").toLong() + val fileKey = (a.get("fileKey") as? String) ?: "" + Cwtch.verifyOrResumeDownload(profile, conversation, fileKey) + } + "SendProfileEvent" -> { + val onion = (a.get("onion") as? String) ?: "" + val jsonEvent = (a.get("jsonEvent") as? String) ?: "" + Cwtch.sendProfileEvent(onion, jsonEvent) + } + "SendAppEvent" -> { + val jsonEvent = (a.get("jsonEvent") as? String) ?: "" + Cwtch.sendAppEvent(jsonEvent) + } + "ResetTor" -> { + Cwtch.resetTor() + } + "ImportBundle" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val bundle = (a.get("bundle") as? String) ?: "" + Cwtch.importBundle(profile, bundle) + } + "CreateGroup" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val server = (a.get("server") as? String) ?: "" + val groupName = (a.get("groupName") as? String) ?: "" + Cwtch.createGroup(profile, server, groupName) + } + "DeleteProfile" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val pass = (a.get("pass") as? String) ?: "" + Cwtch.deleteProfile(profile, pass) + } + "ArchiveConversation" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val conversation = a.getInt("conversation").toLong() + Cwtch.archiveConversation(profile, conversation) + } + "DeleteConversation" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val conversation = a.getInt("conversation").toLong() + Cwtch.deleteContact(profile, conversation) + } + "SetProfileAttribute" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val key = (a.get("Key") as? String) ?: "" + val v = (a.get("Val") as? String) ?: "" + Cwtch.setProfileAttribute(profile, key, v) + } + "SetConversationAttribute" -> { + val profile = (a.get("ProfileOnion") as? String) ?: "" + val conversation = a.getInt("conversation").toLong() + val key = (a.get("Key") as? String) ?: "" + val v = (a.get("Val") as? String) ?: "" + Cwtch.setConversationAttribute(profile, conversation, key, v) + } + "Shutdown" -> { + Cwtch.shutdownCwtch(); + return Result.success() + } + "LoadServers" -> { + val password = (a.get("Password") as? String) ?: "" + Cwtch.loadServers(password) + } + "CreateServer" -> { + val password = (a.get("Password") as? String) ?: "" + val desc = (a.get("Description") as? String) ?: "" + val autostart = (a.get("Autostart") as? Boolean) ?: false + Cwtch.createServer(password, desc, autostart) + } + "DeleteServer" -> { + val serverOnion = (a.get("ServerOnion") as? String) ?: "" + val password = (a.get("Password") as? String) ?: "" + Cwtch.deleteServer(serverOnion, password) + } + "LaunchServers" -> { + Cwtch.launchServers() + } + "LaunchServer" -> { + val serverOnion = (a.get("ServerOnion") as? String) ?: "" + Cwtch.launchServer(serverOnion) + } + "StopServer" -> { + val serverOnion = (a.get("ServerOnion") as? String) ?: "" + Cwtch.stopServer(serverOnion) + } + "StopServers" -> { + Cwtch.stopServers() + } + "DestroyServers" -> { + Cwtch.destroyServers() + } + "SetServerAttribute" -> { + val serverOnion = (a.get("ServerOnion") as? String) ?: "" + val key = (a.get("Key") as? String) ?: "" + val v = (a.get("Val") as? String) ?: "" + Cwtch.setServerAttribute(serverOnion, key, v) + } + "L10nInit" -> { + notificationSimple = (a.get("notificationSimple") as? String) ?: "New Message" + notificationConversationInfo = (a.get("notificationConversationInfo") as? String) + ?: "New Message From " + } + else -> { + Log.i("FlwtchWorker", "unknown command: " + method); + return Result.failure() } } - "CheckDownloadStatus" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val fileKey = (a.get("fileKey") as? String) ?: "" - Cwtch.checkDownloadStatus(profile, fileKey) - } - "VerifyOrResumeDownload" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val conversation = a.getInt("conversation").toLong() - val fileKey = (a.get("fileKey") as? String) ?: "" - Cwtch.verifyOrResumeDownload(profile, conversation, fileKey) - } - "SendProfileEvent" -> { - val onion = (a.get("onion") as? String) ?: "" - val jsonEvent = (a.get("jsonEvent") as? String) ?: "" - Cwtch.sendProfileEvent(onion, jsonEvent) - } - "SendAppEvent" -> { - val jsonEvent = (a.get("jsonEvent") as? String) ?: "" - Cwtch.sendAppEvent(jsonEvent) - } - "ResetTor" -> { - Cwtch.resetTor() - } - "ImportBundle" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val bundle = (a.get("bundle") as? String) ?: "" - Cwtch.importBundle(profile, bundle) - } - "CreateGroup" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val server = (a.get("server") as? String) ?: "" - val groupName = (a.get("groupName") as? String) ?: "" - Cwtch.createGroup(profile, server, groupName) - } - "DeleteProfile" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val pass = (a.get("pass") as? String) ?: "" - Cwtch.deleteProfile(profile, pass) - } - "ArchiveConversation" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val conversation = a.getInt("conversation").toLong() - Cwtch.archiveConversation(profile, conversation) - } - "DeleteConversation" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val conversation = a.getInt("conversation").toLong() - Cwtch.deleteContact(profile, conversation) - } - "SetProfileAttribute" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val key = (a.get("Key") as? String) ?: "" - val v = (a.get("Val") as? String) ?: "" - Cwtch.setProfileAttribute(profile, key, v) - } - "SetConversationAttribute" -> { - val profile = (a.get("ProfileOnion") as? String) ?: "" - val conversation = a.getInt("conversation").toLong() - val key = (a.get("Key") as? String) ?: "" - val v = (a.get("Val") as? String) ?: "" - Cwtch.setConversationAttribute(profile, conversation, key, v) - } - "Shutdown" -> { - Cwtch.shutdownCwtch(); - return Result.success() - } - "LoadServers" -> { - val password = (a.get("Password") as? String) ?: "" - Cwtch.loadServers(password) - } - "CreateServer" -> { - val password = (a.get("Password") as? String) ?: "" - val desc = (a.get("Description") as? String) ?: "" - val autostart = (a.get("Autostart") as? Boolean) ?: false - Cwtch.createServer(password, desc, autostart) - } - "DeleteServer" -> { - val serverOnion = (a.get("ServerOnion") as? String) ?: "" - val password = (a.get("Password") as? String) ?: "" - Cwtch.deleteServer(serverOnion, password) - } - "LaunchServers" -> { - Cwtch.launchServers() - } - "LaunchServer" -> { - val serverOnion = (a.get("ServerOnion") as? String) ?: "" - Cwtch.launchServer(serverOnion) - } - "StopServer" -> { - val serverOnion = (a.get("ServerOnion") as? String) ?: "" - Cwtch.stopServer(serverOnion) - } - "StopServers" -> { - Cwtch.stopServers() - } - "DestroyServers" -> { - Cwtch.destroyServers() - } - "SetServerAttribute" -> { - val serverOnion = (a.get("ServerOnion") as? String) ?: "" - val key = (a.get("Key") as? String) ?: "" - val v = (a.get("Val") as? String) ?: "" - Cwtch.setServerAttribute(serverOnion, key, v) - } - "L10nInit" -> { - notificationSimple = (a.get("notificationSimple") as? String) ?: "New Message" - notificationConversationInfo = (a.get("notificationConversationInfo") as? String) - ?: "New Message From " - } - else -> { - Log.i("FlwtchWorker", "unknown command: " + method); - return Result.failure() - } + return Result.success() + } catch (e: Exception) { + Log.e("FlwtchWorker", "Error in handleCwtch: " + e.toString() + " :: " + e.getStackTrace()) + return Result.failure() } - return Result.success() } // Creates an instance of ForegroundInfo which can be used to update the // ongoing notification. private fun createForegroundInfo(progress: String): ForegroundInfo { val id = "flwtch" - val title = "Flwtch" - val cancel = "Shut down"//todo: translate + val title = "Flwtch" // TODO: change + val cancel = "Shut down" // TODO: translate val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createForegroundNotificationChannel(id, id) diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index ca1a4521..b06040f6 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -100,7 +100,7 @@ abstract class Cwtch { void SetServerAttribute(String serverOnion, String key, String val); // ignore: non_constant_identifier_names - void Shutdown(); + Future Shutdown(); // non-ffi String defaultDownloadPath(); diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 05da5a50..dbeb6c7a 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -317,7 +317,7 @@ class CwtchNotifier { server: groupInvite["ServerHost"], status: status, isGroup: true, - lastMessageTime: DateTime.fromMillisecondsSinceEpoch(0))); + lastMessageTime: DateTime.now())); profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.fromMillisecondsSinceEpoch(0)); } } diff --git a/lib/main.dart b/lib/main.dart index 107c0dc7..882fde45 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -179,9 +179,9 @@ class FlwtchState extends State with WindowListener { } Future shutdown() async { - cwtch.Shutdown(); + await cwtch.Shutdown(); // Wait a few seconds as shutting down things takes a little time.. - Future.delayed(Duration(seconds: 2)).then((value) { + Future.delayed(Duration(seconds: 1)).then((value) { if (Platform.isAndroid) { SystemNavigator.pop(); } else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { diff --git a/lib/models/profile.dart b/lib/models/profile.dart index 1f287401..378b1177 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -80,7 +80,9 @@ class ProfileInfoState extends ChangeNotifier { List servers = jsonDecode(serversJson); this._servers.replace(servers.map((server) { // TODO Keys... - return RemoteServerInfoState(server["onion"], server["identifier"], server["description"], server["status"]); + var preSyncStartTime = DateTime.tryParse(server["syncProgress"]["startTime"]); + var lastMessageTime = DateTime.tryParse(server["syncProgress"]["lastMessageTime"]); + return RemoteServerInfoState(server["onion"], server["identifier"], server["description"], server["status"], lastPreSyncMessageTime: preSyncStartTime, mostRecentMessageTime: lastMessageTime); })); this._contacts.contacts.forEach((contact) { diff --git a/lib/models/remoteserver.dart b/lib/models/remoteserver.dart index 2af03cd4..fb1df327 100644 --- a/lib/models/remoteserver.dart +++ b/lib/models/remoteserver.dart @@ -12,7 +12,12 @@ class RemoteServerInfoState extends ChangeNotifier { double syncProgress = 0; DateTime lastPreSyncMessagTime = new DateTime(2020); - RemoteServerInfoState(this.onion, this.identifier, this.description, this._status); + RemoteServerInfoState(this.onion, this.identifier, this.description, this._status, {lastPreSyncMessageTime, mostRecentMessageTime}) { + if (_status == "Authenticated") { + this.lastPreSyncMessagTime = lastPreSyncMessageTime; + updateSyncProgressFor(mostRecentMessageTime); + } + } void updateDescription(String newDescription) { this.description = newDescription; diff --git a/lib/notification_manager.dart b/lib/notification_manager.dart index 5527ae66..b9ba506e 100644 --- a/lib/notification_manager.dart +++ b/lib/notification_manager.dart @@ -117,7 +117,7 @@ class NixNotificationManager implements NotificationsManager { } NotificationsManager newDesktopNotificationsManager(Future Function(String profileOnion, int convoId) notificationSelectConvo) { - if (Platform.isLinux || Platform.isMacOS) { + if ((Platform.isLinux && !Platform.isAndroid) || Platform.isMacOS) { try { return NixNotificationManager(notificationSelectConvo); } catch (e) { diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index 884fdbfb..a18f4f03 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -32,6 +32,7 @@ class _ContactRowState extends State { visible: contact.isGroup && contact.status == "Authenticated", child: LinearProgressIndicator( color: Provider.of(context).theme.defaultButtonActiveColor, + backgroundColor: Provider.of(context).theme.defaultButtonDisabledColor, value: Provider.of(context).serverList.getServer(contact.server ?? "")?.syncProgress, )); } diff --git a/lib/widgets/remoteserverrow.dart b/lib/widgets/remoteserverrow.dart index fab0076f..90df461c 100644 --- a/lib/widgets/remoteserverrow.dart +++ b/lib/widgets/remoteserverrow.dart @@ -61,6 +61,7 @@ class _RemoteServerRowState extends State { visible: server.status == "Authenticated", child: LinearProgressIndicator( color: Provider.of(context).theme.defaultButtonActiveColor, + backgroundColor: Provider.of(context).theme.defaultButtonDisabledColor, value: server.syncProgress, )), ], diff --git a/pubspec.yaml b/pubspec.yaml index a171f87f..9298931a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.6.1+26 +version: 1.6.2+27 environment: sdk: ">=2.15.0 <3.0.0" From 7cfa9432c843285a4af51f023449f99b793c647a Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Fri, 4 Mar 2022 12:45:48 -0800 Subject: [PATCH 2/2] unbreak notifications on android ([Pp]ictures) + asset dup; fix sync progres resume logic --- .../kotlin/im/cwtch/flwtch/FlwtchWorker.kt | 268 +++++++++--------- lib/cwtch/cwtchNotifier.dart | 1 + lib/main.dart | 8 +- lib/models/remoteserver.dart | 4 +- 4 files changed, 148 insertions(+), 133 deletions(-) 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 38ede391..34cb2961 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -32,7 +32,15 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : private var notificationSimple: String? = null private var notificationConversationInfo: String? = null + override suspend fun doWork(): Result { + // Hack to uncomment and deploy if your device has zombie workers you need to kill + // We need a proper solution but this will clear those out for now + /*if (notificationSimple == null) { + Log.e("FlwtchWorker", "doWork found notificationSimple is null, app has not started, this is a stale thread, terminating") + return Result.failure() + }*/ + val method = inputData.getString(KEY_METHOD) ?: return Result.failure() val args = inputData.getString(KEY_ARGS) @@ -52,7 +60,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : } private fun handleCwtch(method: String, args: String): Result { - try { + val a = JSONObject(args) when (method) { "Start" -> { @@ -66,142 +74,151 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : Log.i("FlwtchWorker.kt", "startCwtch success, starting coroutine AppbusEvent loop...") val downloadIDs = mutableMapOf() while (true) { - val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent()) - // TODO replace this notification block with the NixNotification manager in dart as it has access to contact names and also needs less working around - if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") { - val data = JSONObject(evt.Data) - val handle = data.getString("RemotePeer"); - if (data["RemotePeer"] != data["ProfileOnion"]) { - val notification = data["notification"] + try { + val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent()) + // TODO replace this notification block with the NixNotification manager in dart as it has access to contact names and also needs less working around + if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") { + val data = JSONObject(evt.Data) + val handle = data.getString("RemotePeer"); + val conversationId = data.getInt("ConversationID").toString(); + val notificationChannel = if (evt.EventType == "NewMessageFromPeer") handle else conversationId + if (data["RemotePeer"] != data["ProfileOnion"]) { + val notification = data["notification"] - if (notification == "SimpleEvent") { - val channelId = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createMessageNotificationChannel("Cwtch", "Cwtch") - } else { - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - "" - } + if (notification == "SimpleEvent") { + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createMessageNotificationChannel("Cwtch", "Cwtch") + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + } - val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> - intent.action = Intent.ACTION_RUN - intent.putExtra("EventType", "NotificationClicked") + val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> + intent.action = Intent.ACTION_RUN + intent.putExtra("EventType", "NotificationClicked") + } + + val newNotification = NotificationCompat.Builder(applicationContext, channelId) + .setContentTitle("Cwtch") + .setContentText(notificationSimple ?: "New Message") + .setSmallIcon(R.mipmap.knott_transparent) + .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setAutoCancel(true) + .build() + + notificationManager.notify(getNotificationID("Cwtch", "Cwtch"), newNotification) + } else if (notification == "ContactInfo") { + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createMessageNotificationChannel(notificationChannel, notificationChannel) + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + } + val loader = FlutterInjector.instance().flutterLoader() + Log.i("FlwtchWorker.kt", "notification for " + evt.EventType + " " + handle + " " + conversationId + " " + channelId) + Log.i("FlwtchWorker.kt", data.toString()); + val key = loader.getLookupKeyForAsset(data.getString("picture"))//"assets/profiles/001-centaur.png") + val fh = applicationContext.assets.open(key) + + val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> + intent.action = Intent.ACTION_RUN + intent.putExtra("EventType", "NotificationClicked") + intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) + intent.putExtra("Handle", handle) + } + + val newNotification = NotificationCompat.Builder(applicationContext, channelId) + .setContentTitle(data.getString("Nick")) + .setContentText((notificationConversationInfo + ?: "New Message From %1").replace("%1", data.getString("Nick"))) + .setLargeIcon(BitmapFactory.decodeStream(fh)) + .setSmallIcon(R.mipmap.knott_transparent) + .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setAutoCancel(true) + .build() + + notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), channelId), newNotification) } - val newNotification = NotificationCompat.Builder(applicationContext, channelId) - .setContentTitle("Cwtch") - .setContentText(notificationSimple ?: "New Message") - .setSmallIcon(R.mipmap.knott_transparent) - .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) - .setAutoCancel(true) - .build() - - notificationManager.notify(getNotificationID("Cwtch", "Cwtch"), newNotification) - } else if (notification == "ContactInfo") { - val channelId = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createMessageNotificationChannel(handle, handle) - } else { - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - "" - } - val loader = FlutterInjector.instance().flutterLoader() - val key = loader.getLookupKeyForAsset("assets/" + data.getString("Picture"))//"assets/profiles/001-centaur.png") - val fh = applicationContext.assets.open(key) - - val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> - intent.action = Intent.ACTION_RUN - intent.putExtra("EventType", "NotificationClicked") - intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) - intent.putExtra("Handle", handle) - } - - val newNotification = NotificationCompat.Builder(applicationContext, channelId) - .setContentTitle(data.getString("Nick")) - .setContentText((notificationConversationInfo - ?: "New Message From %1").replace("%1", data.getString("Nick"))) - .setLargeIcon(BitmapFactory.decodeStream(fh)) - .setSmallIcon(R.mipmap.knott_transparent) - .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) - .setAutoCancel(true) - .build() - - notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification) } - - } - } else if (evt.EventType == "FileDownloadProgressUpdate") { - try { + } else if (evt.EventType == "FileDownloadProgressUpdate") { + try { + val data = JSONObject(evt.Data); + val fileKey = data.getString("FileKey"); + val title = data.getString("NameSuggestion"); + val progress = data.getString("Progress").toInt(); + val progressMax = data.getString("FileSizeInChunks").toInt(); + if (!downloadIDs.containsKey(fileKey)) { + downloadIDs.put(fileKey, downloadIDs.count()); + } + var dlID = downloadIDs.get(fileKey); + if (dlID == null) { + dlID = 0; + } + if (progress >= 0) { + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createDownloadNotificationChannel(fileKey, fileKey) + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + }; + val newNotification = NotificationCompat.Builder(applicationContext, channelId) + .setOngoing(true) + .setContentTitle("Downloading")//todo: translate + .setContentText(title) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setProgress(progressMax, progress, false) + .setSound(null) + //.setSilent(true) + .build(); + notificationManager.notify(dlID, newNotification); + } + } catch (e: Exception) { + Log.d("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace()); + } + } else if (evt.EventType == "FileDownloaded") { + Log.d("FlwtchWorker", "file downloaded!"); val data = JSONObject(evt.Data); + val tempFile = data.getString("TempFile"); val fileKey = data.getString("FileKey"); - val title = data.getString("NameSuggestion"); - val progress = data.getString("Progress").toInt(); - val progressMax = data.getString("FileSizeInChunks").toInt(); - if (!downloadIDs.containsKey(fileKey)) { - downloadIDs.put(fileKey, downloadIDs.count()); + if (tempFile != "" && tempFile != data.getString("FilePath")) { + val filePath = data.getString("FilePath"); + Log.i("FlwtchWorker", "moving " + tempFile + " to " + filePath); + val sourcePath = Paths.get(tempFile); + val targetUri = Uri.parse(filePath); + val os = this.applicationContext.getContentResolver().openOutputStream(targetUri); + val bytesWritten = Files.copy(sourcePath, os); + Log.d("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes"); + if (bytesWritten != 0L) { + os?.flush(); + os?.close(); + Files.delete(sourcePath); + } } - var dlID = downloadIDs.get(fileKey); - if (dlID == null) { - dlID = 0; - } - if (progress >= 0) { - val channelId = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createDownloadNotificationChannel(fileKey, fileKey) - } else { - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - "" - }; - val newNotification = NotificationCompat.Builder(applicationContext, channelId) - .setOngoing(true) - .setContentTitle("Downloading")//todo: translate - .setContentText(title) - .setSmallIcon(android.R.drawable.stat_sys_download) - .setProgress(progressMax, progress, false) - .setSound(null) - //.setSilent(true) - .build(); - notificationManager.notify(dlID, newNotification); - } - } catch (e: Exception) { - Log.d("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace()); - } - } else if (evt.EventType == "FileDownloaded") { - Log.d("FlwtchWorker", "file downloaded!"); - val data = JSONObject(evt.Data); - val tempFile = data.getString("TempFile"); - val fileKey = data.getString("FileKey"); - if (tempFile != "" && tempFile != data.getString("FilePath")) { - val filePath = data.getString("FilePath"); - Log.i("FlwtchWorker", "moving " + tempFile + " to " + filePath); - val sourcePath = Paths.get(tempFile); - val targetUri = Uri.parse(filePath); - val os = this.applicationContext.getContentResolver().openOutputStream(targetUri); - val bytesWritten = Files.copy(sourcePath, os); - Log.d("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes"); - if (bytesWritten != 0L) { - os?.flush(); - os?.close(); - Files.delete(sourcePath); + if (downloadIDs.containsKey(fileKey)) { + notificationManager.cancel(downloadIDs.get(fileKey) ?: 0); } } - if (downloadIDs.containsKey(fileKey)) { - notificationManager.cancel(downloadIDs.get(fileKey) ?: 0); - } - } - Intent().also { intent -> - intent.action = "im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS" - intent.putExtra("EventType", evt.EventType) - intent.putExtra("Data", evt.Data) - intent.putExtra("EventID", evt.EventID) - LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent) + Intent().also { intent -> + intent.action = "im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS" + intent.putExtra("EventType", evt.EventType) + intent.putExtra("Data", evt.Data) + intent.putExtra("EventID", evt.EventID) + LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent) + } + } catch (e: Exception) { + Log.e("FlwtchWorker", "Error in handleCwtch: " + e.toString() + " :: " + e.getStackTrace()); } } } + "ReconnectCwtchForeground" -> { Cwtch.reconnectCwtchForeground() } @@ -408,11 +425,8 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : return Result.failure() } } + return Result.success() - } catch (e: Exception) { - Log.e("FlwtchWorker", "Error in handleCwtch: " + e.toString() + " :: " + e.getStackTrace()) - return Result.failure() - } } // Creates an instance of ForegroundInfo which can be used to update the diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index dbeb6c7a..7fbf4022 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -153,6 +153,7 @@ class CwtchNotifier { } break; case "NewMessageFromPeer": + var identifier = int.parse(data["ConversationID"]); var messageID = int.parse(data["Index"]); var timestamp = DateTime.tryParse(data['TimestampReceived'])!; diff --git a/lib/main.dart b/lib/main.dart index 882fde45..d85569f7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -63,9 +63,9 @@ class FlwtchState extends State with WindowListener { final GlobalKey navKey = GlobalKey(); - Future shutdownDirect(MethodCall call) { + Future shutdownDirect(MethodCall call) async { print(call); - cwtch.Shutdown(); + await cwtch.Shutdown(); return Future.value({}); } @@ -247,8 +247,8 @@ class FlwtchState extends State with WindowListener { } @override - void dispose() { - cwtch.Shutdown(); + void dispose() async { + await cwtch.Shutdown(); windowManager.removeListener(this); cwtch.dispose(); super.dispose(); diff --git a/lib/models/remoteserver.dart b/lib/models/remoteserver.dart index fb1df327..f53ef31b 100644 --- a/lib/models/remoteserver.dart +++ b/lib/models/remoteserver.dart @@ -50,8 +50,8 @@ class RemoteServerInfoState extends ChangeNotifier { // updateSyncProgressFor point takes a message's time, and updates the server sync progress, // based on that point in time between the precalculated lastPreSyncMessagTime and Now void updateSyncProgressFor(DateTime point) { - var range = lastPreSyncMessagTime.difference(DateTime.now()); - var pointFromStart = lastPreSyncMessagTime.difference(point); + var range = lastPreSyncMessagTime.toUtc().difference(DateTime.now().toUtc()); + var pointFromStart = lastPreSyncMessagTime.toUtc().difference(point.toUtc()); syncProgress = pointFromStart.inSeconds / range.inSeconds; notifyListeners(); }